OSDN Git Service

auto import from //branches/cupcake/...@130745
authorThe Android Open Source Project <initial-contribution@android.com>
Tue, 10 Feb 2009 23:43:58 +0000 (15:43 -0800)
committerThe Android Open Source Project <initial-contribution@android.com>
Tue, 10 Feb 2009 23:43:58 +0000 (15:43 -0800)
95 files changed:
apps/Fallback/res/values-ko/strings.xml [new file with mode: 0644]
apps/Fallback/res/values-nb/strings.xml [new file with mode: 0644]
apps/SpareParts/res/values/strings.xml
apps/SpareParts/res/xml/spare_parts.xml
apps/SpareParts/src/com/android/spare_parts/SpareParts.java
apps/Term/AndroidManifest.xml
apps/Term/src/com/android/term/Term.java
build/sdk.atree
host/windows/prebuilt/AdbWinApi.dll [deleted file]
host/windows/prebuilt/usb/AdbWinApi.dll
host/windows/prebuilt/usb/driver/android_usb.inf
host/windows/prebuilt/usb/driver_amd_64/android_usb.inf
host/windows/usb/api/adb_interface.cpp
host/windows/usb/driver/android_usb.inf
ide/eclipse/.classpath
pdk/docs/instrumentation_testing.html
pdk/ndk/Android_NDK_README.html [new file with mode: 0644]
pdk/ndk/Ndk.mk
pdk/ndk/README [deleted file]
samples/ApiDemos/AndroidManifest.xml
samples/ApiDemos/res/layout/reorder_four.xml [new file with mode: 0644]
samples/ApiDemos/res/layout/reorder_on_launch.xml [new file with mode: 0644]
samples/ApiDemos/res/layout/reorder_three.xml [new file with mode: 0644]
samples/ApiDemos/res/layout/reorder_two.xml [new file with mode: 0644]
samples/ApiDemos/res/values/strings.xml
samples/ApiDemos/res/xml/searchable.xml
samples/ApiDemos/src/com/example/android/apis/app/ReorderFour.java [new file with mode: 0644]
samples/ApiDemos/src/com/example/android/apis/app/ReorderOnLaunch.java [new file with mode: 0644]
samples/ApiDemos/src/com/example/android/apis/app/ReorderThree.java [new file with mode: 0644]
samples/ApiDemos/src/com/example/android/apis/app/ReorderTwo.java [new file with mode: 0644]
samples/ApiDemos/src/com/example/android/apis/app/VoiceRecognition.java [moved from samples/ApiDemos/src/com/android/samples/app/VoiceRecognition.java with 98% similarity]
samples/ApiDemos/src/com/example/android/apis/graphics/ProxyDrawable.java
samples/GlobalTime/src/com/android/globaltime/GlobalTime.java
samples/SoftKeyboard/res/values-land/dimens.xml
samples/SoftKeyboard/res/values/dimens.xml
samples/SoftKeyboard/src/com/example/android/softkeyboard/SoftKeyboard.java
simulator/app/PropertyServer.cpp
simulator/wrapsim/DevPower.c
simulator/wrapsim/FakeDev.c
tools/androidprefs/src/com/android/prefs/AndroidLocation.java
tools/apkbuilder/src/com/android/apkbuilder/ApkBuilder.java
tools/ddms/libs/ddmlib/src/com/android/ddmlib/AdbHelper.java
tools/ddms/libs/ddmlib/src/com/android/ddmlib/Device.java
tools/ddms/libs/ddmlib/src/com/android/ddmlib/DeviceMonitor.java
tools/ddms/libs/ddmlib/src/com/android/ddmlib/EmulatorConsole.java
tools/ddms/libs/ddmlib/src/com/android/ddmlib/IDevice.java
tools/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunnerTest.java
tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/DevicePanel.java
tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplayFilteredLog.java [new file with mode: 0644]
tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplayGraph.java [new file with mode: 0644]
tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplayLog.java [new file with mode: 0644]
tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplaySync.java [new file with mode: 0644]
tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplaySyncHistogram.java [new file with mode: 0644]
tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/EventDisplay.java
tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/EventDisplayOptions.java
tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/EventLogImporter.java
tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java
tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/BaseBuilder.java
tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java
tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ResourceManagerBuilder.java
tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/AndroidLaunchController.java
tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/DeviceChooserDialog.java
tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/LaunchConfigDelegate.java
tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/EmulatorConfigTab.java
tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ProjectHelper.java
tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/Sdk.java
tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/descriptors/CustomViewDescriptorService.java
tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiAbstractTextAttributeNode.java
tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/project/ProjectHelperTest.java
tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/sdk/AndroidJarLoaderTest.java
tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/sdk/LayoutParamsParserTest.java
tools/findunused/findunusedresources
tools/runtest
tools/scripts/app_engine_server/LICENSE [new file with mode: 0644]
tools/scripts/app_engine_server/app.yaml [new file with mode: 0755]
tools/scripts/app_engine_server/gae_shell/README [new file with mode: 0644]
tools/scripts/app_engine_server/gae_shell/__init__.py [new file with mode: 0644]
tools/scripts/app_engine_server/gae_shell/__init__.pyc [new file with mode: 0644]
tools/scripts/app_engine_server/gae_shell/shell.py [new file with mode: 0755]
tools/scripts/app_engine_server/gae_shell/shell.py~ [new file with mode: 0755]
tools/scripts/app_engine_server/gae_shell/static/shell.js [new file with mode: 0644]
tools/scripts/app_engine_server/gae_shell/static/spinner.gif [new file with mode: 0644]
tools/scripts/app_engine_server/gae_shell/templates/shell.html [new file with mode: 0644]
tools/scripts/app_engine_server/index.yaml [new file with mode: 0644]
tools/scripts/app_engine_server/memcache_zipserve.py [new file with mode: 0644]
tools/scripts/combine_sdks.sh
tools/sdkmanager/app/src/com/android/sdkmanager/CommandLineProcessor.java
tools/sdkmanager/app/src/com/android/sdkmanager/Main.java
tools/sdkmanager/app/src/com/android/sdkmanager/SdkCommandLine.java
tools/sdkmanager/app/tests/com/android/sdkmanager/CommandLineProcessorTest.java
tools/sdkmanager/app/tests/com/android/sdkmanager/SdkCommandLineTest.java
tools/sdkmanager/libs/sdklib/src/com/android/sdklib/ISdkLog.java
tools/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java [moved from tools/sdkmanager/libs/sdklib/src/com/android/sdklib/vm/VmManager.java with 73% similarity]
tools/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/HardwareProperties.java [moved from tools/sdkmanager/libs/sdklib/src/com/android/sdklib/vm/HardwareProperties.java with 99% similarity]
tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/AvdSelector.java [moved from tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/VmSelector.java with 87% similarity]

diff --git a/apps/Fallback/res/values-ko/strings.xml b/apps/Fallback/res/values-ko/strings.xml
new file mode 100644 (file)
index 0000000..1e98e20
--- /dev/null
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="appTitle">"Fallback"</string>
+    <string name="title">"지원되지 않는 작업"</string>
+    <string name="error">"이 작업은 현재 지원되지 않습니다."</string>
+</resources>
diff --git a/apps/Fallback/res/values-nb/strings.xml b/apps/Fallback/res/values-nb/strings.xml
new file mode 100644 (file)
index 0000000..6fed660
--- /dev/null
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="appTitle">"Fallback"</string>
+    <string name="title">"Ustøttet handling"</string>
+    <string name="error">"Denne handlingen er ikke støttet nå."</string>
+</resources>
index 8565a52..7c776d8 100644 (file)
     <string name="summary_transition_animations">Speed of animations moving between screens</string>
     <string name="dialog_title_transition_animations">Select transition speed</string>
     
+    <string name="title_fancy_ime_animations">Fancy input animations</string>
+    <string name="summary_on_fancy_ime_animations">Use fancier animations for input method windows</string>
+    <string name="summary_off_fancy_ime_animations">Use normal animations for input method windows</string>
+    
     <string name="title_font_size">Font size</string>
     <string name="summary_font_size">Overall size of fonts</string>
     <string name="dialog_title_font_size">Select font size</string>
index ce673e3..8608896 100644 (file)
                 android:entryValues="@array/entryvalues_animations"
                 android:dialogTitle="@string/dialog_title_transition_animations" />
         
+        <CheckBoxPreference 
+            android:key="fancy_ime_animations" 
+            android:title="@string/title_fancy_ime_animations" 
+            android:summaryOn="@string/summary_on_fancy_ime_animations"
+            android:summaryOff="@string/summary_off_fancy_ime_animations"/>
+        
         <ListPreference
                 android:key="font_size"
                 android:title="@string/title_font_size"
index 9de9a45..facaf69 100644 (file)
@@ -42,6 +42,7 @@ public class SpareParts extends PreferenceActivity
 
     private static final String WINDOW_ANIMATIONS_PREF = "window_animations";
     private static final String TRANSITION_ANIMATIONS_PREF = "transition_animations";
+    private static final String FANCY_IME_ANIMATIONS_PREF = "fancy_ime_animations";
     private static final String FONT_SIZE_PREF = "font_size";
     private static final String END_BUTTON_PREF = "end_button";
     private static final String ACCELEROMETER_PREF = "accelerometer";
@@ -51,6 +52,7 @@ public class SpareParts extends PreferenceActivity
     
     private ListPreference mWindowAnimationsPref;
     private ListPreference mTransitionAnimationsPref;
+    private CheckBoxPreference mFancyImeAnimationsPref;
     private ListPreference mFontSizePref;
     private ListPreference mEndButtonPref;
     private CheckBoxPreference mAccelerometerPref;
@@ -69,6 +71,7 @@ public class SpareParts extends PreferenceActivity
         mWindowAnimationsPref.setOnPreferenceChangeListener(this);
         mTransitionAnimationsPref = (ListPreference) prefSet.findPreference(TRANSITION_ANIMATIONS_PREF);
         mTransitionAnimationsPref.setOnPreferenceChangeListener(this);
+        mFancyImeAnimationsPref = (CheckBoxPreference) prefSet.findPreference(FANCY_IME_ANIMATIONS_PREF);
         mFontSizePref = (ListPreference) prefSet.findPreference(FONT_SIZE_PREF);
         mFontSizePref.setOnPreferenceChangeListener(this);
         mEndButtonPref = (ListPreference) prefSet.findPreference(END_BUTTON_PREF);
@@ -83,6 +86,9 @@ public class SpareParts extends PreferenceActivity
 
     private void updateToggles() {
         try {
+            mFancyImeAnimationsPref.setChecked(Settings.System.getInt(
+                    getContentResolver(), 
+                    Settings.System.FANCY_IME_ANIMATIONS, 0) != 0);
             mAccelerometerPref.setChecked(Settings.System.getInt(
                     getContentResolver(), 
                     Settings.System.ACCELEROMETER_ROTATION, 0) != 0);
@@ -181,6 +187,10 @@ public class SpareParts extends PreferenceActivity
             Settings.System.putInt(getContentResolver(),
                     Settings.System.ACCELEROMETER_ROTATION,
                     mAccelerometerPref.isChecked() ? 1 : 0);
+        } else if (FANCY_IME_ANIMATIONS_PREF.equals(key)) {
+            Settings.System.putInt(getContentResolver(),
+                    Settings.System.FANCY_IME_ANIMATIONS,
+                    mFancyImeAnimationsPref.isChecked() ? 1 : 0);
         } else if (MAPS_COMPASS_PREF.equals(key)) {
             try {
                 Context c = createPackageContext("com.google.android.apps.maps", 0);
index 8975fa4..7084d2c 100644 (file)
@@ -4,7 +4,8 @@
         <activity android:name="Term"
                 android:theme="@style/Theme"
                 android:launchMode="singleInstance"
-                android:configChanges="keyboard|keyboardHidden|orientation">
+                android:configChanges="keyboard|keyboardHidden|orientation"
+                android:windowSoftInputMode="adjustResize|stateVisible">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.TEST" />
index 55cee3c..a621ffd 100644 (file)
@@ -48,6 +48,12 @@ import android.view.Menu;
 import android.view.MenuItem;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ExtractedText;
+import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
 
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
@@ -177,6 +183,9 @@ public class Term extends Activity {
 
         mKeyListener = new TermKeyListener();
 
+        mEmulatorView.setFocusable(true);
+        mEmulatorView.requestFocus();
+
         updatePrefs();
     }
 
@@ -2583,7 +2592,6 @@ class EmulatorView extends View implements GestureDetector.OnGestureListener {
     private FileOutputStream mTermOut;
 
     private ByteQueue mByteQueue;
-    private final static int MAX_BYTES_PER_UPDATE = 4 * 1024;
 
     /**
      * Used to temporarily hold data received from the remote process. Allocated
@@ -2643,6 +2651,117 @@ class EmulatorView extends View implements GestureDetector.OnGestureListener {
         invalidate();
     }
 
+    @Override
+    public boolean onCheckIsTextEditor() {
+        return true;
+    }
+
+    @Override
+    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+        return new InputConnection(){
+
+            public boolean beginBatchEdit() {
+                return true;
+            }
+
+            public boolean clearMetaKeyStates(int states) {
+                return true;
+            }
+
+            public boolean commitCompletion(CompletionInfo text) {
+                return true;
+            }
+
+            public boolean commitText(CharSequence text, int newCursorPosition) {
+                sendText(text);
+                return true;
+            }
+
+            public boolean deleteSurroundingText(int leftLength, int rightLength) {
+                return true;
+            }
+
+            public boolean endBatchEdit() {
+                return true;
+            }
+
+            public boolean finishComposingText() {
+                return true;
+            }
+
+            public int getCursorCapsMode(int reqModes) {
+                return 0;
+            }
+
+            public ExtractedText getExtractedText(ExtractedTextRequest request,
+                    int flags) {
+                return null;
+            }
+
+            public CharSequence getTextAfterCursor(int n, int flags) {
+                return null;
+            }
+
+            public CharSequence getTextBeforeCursor(int n, int flags) {
+                return null;
+            }
+
+            public boolean hideStatusIcon() {
+                return true;
+            }
+
+            public boolean performContextMenuAction(int id) {
+                return true;
+            }
+
+            public boolean performPrivateCommand(String action, Bundle data) {
+                return true;
+            }
+
+            public boolean sendKeyEvent(KeyEvent event) {
+                switch(event.getKeyCode()) {
+                case KeyEvent.KEYCODE_ENTER:
+                    sendChar('\r');
+                    break;
+                case KeyEvent.KEYCODE_DEL:
+                    sendChar(127);
+                    break;
+                }
+                return true;
+            }
+
+            public boolean setComposingText(CharSequence text, int newCursorPosition) {
+                return true;
+            }
+
+            public boolean setSelection(int start, int end) {
+                return true;
+            }
+
+            public boolean showStatusIcon(String packageName, int resId) {
+                return true;
+            }
+
+            private void sendChar(int c) {
+                try {
+                    mTermOut.write(c);
+                } catch (IOException ex) {
+
+                }
+            }
+            private void sendText(CharSequence text) {
+                int n = text.length();
+                try {
+                    for(int i = 0; i < n; i++) {
+                        char c = text.charAt(i);
+                        mTermOut.write(c);
+                    }
+                } catch (IOException e) {
+                }
+            }
+        };
+    }
+
     public boolean getKeypadApplicationMode() {
         return mEmulator.getKeypadApplicationMode();
     }
index b9126d4..986ce62 100644 (file)
@@ -126,7 +126,7 @@ system.img platforms/${PLATFORM_NAME}/images/system.img
 ramdisk.img platforms/${PLATFORM_NAME}/images/ramdisk.img
 userdata.img platforms/${PLATFORM_NAME}/images/userdata.img
 prebuilt/android-arm/kernel/kernel-qemu platforms/${PLATFORM_NAME}/images/kernel-qemu
-external/qemu/android/vm/hardware-properties.ini tools/lib/hardware-properties.ini
+external/qemu/android/avd/hardware-properties.ini tools/lib/hardware-properties.ini
 
 # emulator skins
 development/emulator/skins/HVGA   platforms/${PLATFORM_NAME}/skins/HVGA
diff --git a/host/windows/prebuilt/AdbWinApi.dll b/host/windows/prebuilt/AdbWinApi.dll
deleted file mode 100644 (file)
index aa8e956..0000000
Binary files a/host/windows/prebuilt/AdbWinApi.dll and /dev/null differ
index 1626bce..9670b4f 100755 (executable)
Binary files a/host/windows/prebuilt/usb/AdbWinApi.dll and b/host/windows/prebuilt/usb/AdbWinApi.dll differ
index 2837a1b..fff0440 100644 (file)
@@ -10,7 +10,7 @@ Signature="$WINDOWS NT$"
 Class=USB
 ClassGuid={F72FE0D4-CBCB-407d-8814-9ED673D0DD6B}
 Provider=%GOOG%
-DriverVer=12/11/2008,1.0.0009.00000
+DriverVer=1/29/2009,1.0.0010.00000
 CatalogFile.NTx86=androidusb86.cat
 CatalogFile.NTamd64=androidusba64.cat
 
@@ -38,6 +38,7 @@ DefaultDestDir = 12
 ; HTC Dream
 %USB\VID_0BB4&PID_0C01.DeviceDescRelease%=androidusb.Dev, USB\VID_0BB4&PID_0C01
 %USB\VID_0BB4&PID_0C02&MI_01.DeviceDescRelease%=androidusb.Dev, USB\VID_0BB4&PID_0C02&MI_01
+%USB\VID_0BB4&PID_0FFF.DeviceDescRelease%=androidusb.Dev, USB\VID_0BB4&PID_0FFF
 
 ; For XP and later
 [Google.NTx86]
@@ -46,6 +47,7 @@ DefaultDestDir = 12
 ; HTC Dream
 %USB\VID_0BB4&PID_0C01.DeviceDescRelease%=androidusb.Dev, USB\VID_0BB4&PID_0C01
 %USB\VID_0BB4&PID_0C02&MI_01.DeviceDescRelease%=androidusb.Dev, USB\VID_0BB4&PID_0C02&MI_01
+%USB\VID_0BB4&PID_0FFF.DeviceDescRelease%=androidusb.Dev, USB\VID_0BB4&PID_0FFF
 
 ; For AMD64 and later
 [Google.NTamd64]
@@ -54,6 +56,7 @@ DefaultDestDir = 12
 ; HTC Dream
 %USB\VID_0BB4&PID_0C01.DeviceDescRelease%=androidusb.Dev, USB\VID_0BB4&PID_0C01
 %USB\VID_0BB4&PID_0C02&MI_01.DeviceDescRelease%=androidusb.Dev, USB\VID_0BB4&PID_0C02&MI_01
+%USB\VID_0BB4&PID_0FFF.DeviceDescRelease%=androidusb.Dev, USB\VID_0BB4&PID_0FFF
 
 [androidusb.Dev.NT]
 CopyFiles=androidusb.Files.Ext
@@ -120,3 +123,4 @@ ClassName       = "ADB Interface"
 USB\VID_18D1&PID_DDDD.DeviceDescTest="ADB Testing Interface"
 USB\VID_0BB4&PID_0C01.DeviceDescRelease="HTC Dream"
 USB\VID_0BB4&PID_0C02&MI_01.DeviceDescRelease="HTC Dream Composite ADB Interface"
+USB\VID_0BB4&PID_0FFF.DeviceDescRelease="HTC Bootloader"
index 2837a1b..fff0440 100644 (file)
@@ -10,7 +10,7 @@ Signature="$WINDOWS NT$"
 Class=USB
 ClassGuid={F72FE0D4-CBCB-407d-8814-9ED673D0DD6B}
 Provider=%GOOG%
-DriverVer=12/11/2008,1.0.0009.00000
+DriverVer=1/29/2009,1.0.0010.00000
 CatalogFile.NTx86=androidusb86.cat
 CatalogFile.NTamd64=androidusba64.cat
 
@@ -38,6 +38,7 @@ DefaultDestDir = 12
 ; HTC Dream
 %USB\VID_0BB4&PID_0C01.DeviceDescRelease%=androidusb.Dev, USB\VID_0BB4&PID_0C01
 %USB\VID_0BB4&PID_0C02&MI_01.DeviceDescRelease%=androidusb.Dev, USB\VID_0BB4&PID_0C02&MI_01
+%USB\VID_0BB4&PID_0FFF.DeviceDescRelease%=androidusb.Dev, USB\VID_0BB4&PID_0FFF
 
 ; For XP and later
 [Google.NTx86]
@@ -46,6 +47,7 @@ DefaultDestDir = 12
 ; HTC Dream
 %USB\VID_0BB4&PID_0C01.DeviceDescRelease%=androidusb.Dev, USB\VID_0BB4&PID_0C01
 %USB\VID_0BB4&PID_0C02&MI_01.DeviceDescRelease%=androidusb.Dev, USB\VID_0BB4&PID_0C02&MI_01
+%USB\VID_0BB4&PID_0FFF.DeviceDescRelease%=androidusb.Dev, USB\VID_0BB4&PID_0FFF
 
 ; For AMD64 and later
 [Google.NTamd64]
@@ -54,6 +56,7 @@ DefaultDestDir = 12
 ; HTC Dream
 %USB\VID_0BB4&PID_0C01.DeviceDescRelease%=androidusb.Dev, USB\VID_0BB4&PID_0C01
 %USB\VID_0BB4&PID_0C02&MI_01.DeviceDescRelease%=androidusb.Dev, USB\VID_0BB4&PID_0C02&MI_01
+%USB\VID_0BB4&PID_0FFF.DeviceDescRelease%=androidusb.Dev, USB\VID_0BB4&PID_0FFF
 
 [androidusb.Dev.NT]
 CopyFiles=androidusb.Files.Ext
@@ -120,3 +123,4 @@ ClassName       = "ADB Interface"
 USB\VID_18D1&PID_DDDD.DeviceDescTest="ADB Testing Interface"
 USB\VID_0BB4&PID_0C01.DeviceDescRelease="HTC Dream"
 USB\VID_0BB4&PID_0C02&MI_01.DeviceDescRelease="HTC Dream Composite ADB Interface"
+USB\VID_0BB4&PID_0FFF.DeviceDescRelease="HTC Bootloader"
index 4f6c801..9369e13 100644 (file)
@@ -106,7 +106,7 @@ bool AdbInterfaceObject::GetSerialNumber(void* buffer,
 
   // Open USB device for this intefface
   HANDLE usb_device_handle = CreateFile(interface_name().c_str(),
-                                        FILE_READ_ATTRIBUTES | FILE_READ_EA,
+                                        GENERIC_READ,
                                         FILE_SHARE_READ | FILE_SHARE_WRITE,
                                         NULL,
                                         OPEN_EXISTING,
@@ -208,7 +208,7 @@ bool AdbInterfaceObject::GetEndpointInformation(UCHAR endpoint_index,
 
   // Open USB device for this intefface
   HANDLE usb_device_handle = CreateFile(interface_name().c_str(),
-                                        FILE_READ_ATTRIBUTES | FILE_READ_EA,
+                                        GENERIC_READ,
                                         FILE_SHARE_READ | FILE_SHARE_WRITE,
                                         NULL,
                                         OPEN_EXISTING,
index 2837a1b..fff0440 100644 (file)
@@ -10,7 +10,7 @@ Signature="$WINDOWS NT$"
 Class=USB
 ClassGuid={F72FE0D4-CBCB-407d-8814-9ED673D0DD6B}
 Provider=%GOOG%
-DriverVer=12/11/2008,1.0.0009.00000
+DriverVer=1/29/2009,1.0.0010.00000
 CatalogFile.NTx86=androidusb86.cat
 CatalogFile.NTamd64=androidusba64.cat
 
@@ -38,6 +38,7 @@ DefaultDestDir = 12
 ; HTC Dream
 %USB\VID_0BB4&PID_0C01.DeviceDescRelease%=androidusb.Dev, USB\VID_0BB4&PID_0C01
 %USB\VID_0BB4&PID_0C02&MI_01.DeviceDescRelease%=androidusb.Dev, USB\VID_0BB4&PID_0C02&MI_01
+%USB\VID_0BB4&PID_0FFF.DeviceDescRelease%=androidusb.Dev, USB\VID_0BB4&PID_0FFF
 
 ; For XP and later
 [Google.NTx86]
@@ -46,6 +47,7 @@ DefaultDestDir = 12
 ; HTC Dream
 %USB\VID_0BB4&PID_0C01.DeviceDescRelease%=androidusb.Dev, USB\VID_0BB4&PID_0C01
 %USB\VID_0BB4&PID_0C02&MI_01.DeviceDescRelease%=androidusb.Dev, USB\VID_0BB4&PID_0C02&MI_01
+%USB\VID_0BB4&PID_0FFF.DeviceDescRelease%=androidusb.Dev, USB\VID_0BB4&PID_0FFF
 
 ; For AMD64 and later
 [Google.NTamd64]
@@ -54,6 +56,7 @@ DefaultDestDir = 12
 ; HTC Dream
 %USB\VID_0BB4&PID_0C01.DeviceDescRelease%=androidusb.Dev, USB\VID_0BB4&PID_0C01
 %USB\VID_0BB4&PID_0C02&MI_01.DeviceDescRelease%=androidusb.Dev, USB\VID_0BB4&PID_0C02&MI_01
+%USB\VID_0BB4&PID_0FFF.DeviceDescRelease%=androidusb.Dev, USB\VID_0BB4&PID_0FFF
 
 [androidusb.Dev.NT]
 CopyFiles=androidusb.Files.Ext
@@ -120,3 +123,4 @@ ClassName       = "ADB Interface"
 USB\VID_18D1&PID_DDDD.DeviceDescTest="ADB Testing Interface"
 USB\VID_0BB4&PID_0C01.DeviceDescRelease="HTC Dream"
 USB\VID_0BB4&PID_0C02&MI_01.DeviceDescRelease="HTC Dream Composite ADB Interface"
+USB\VID_0BB4&PID_0FFF.DeviceDescRelease="HTC Bootloader"
index 0e3f43e..a871dc0 100644 (file)
@@ -19,7 +19,6 @@
        <classpathentry kind="src" path="packages/apps/Settings/src"/>
        <classpathentry kind="src" path="packages/apps/SoundRecorder/src"/>
        <classpathentry kind="src" path="packages/apps/Stk/src"/>
-       <classpathentry kind="src" path="packages/apps/Sync/src"/>
        <classpathentry kind="src" path="packages/apps/Updater/src"/>
        <classpathentry kind="src" path="packages/apps/VoiceDialer/src"/>
        <classpathentry kind="src" path="packages/providers/CalendarProvider/src"/>
index bac3ca8..931fd77 100755 (executable)
@@ -200,7 +200,7 @@ h1,h2,h3 {
 <a href="#androidTestingLocationFiles">Location of Files</a><br/>
 <a href="#androidTestingContentMakefile">Contents of makefile</a><br/>
 <a href="#androidTestingContentManifest">Content of Manifest</a><br/>
-<a href="#androidInstrumentationTestingCreatingTestRunner">New Instrumentation TestRunner</a><br/>
+<a href="#androidInstrumentationTestingCreatingTestRunner">New InstrumentationTestRunner</a><br/>
 <a href="#androidInstrumentationTestingCreatingTestCase">New InstrumentationTestCase</a><br/>
 <a href="#androidInstrumentationFrameworkTestCase">Exploring a Test Case</a><br/>
 <a href="#androidTestingKindsofTests">Deciding Kinds of Tests to Write</a><br/></div>
@@ -493,23 +493,28 @@ include $(BUILD_PACKAGE)
 
 <a name="androidTestingContentManifest"></a><h3>Content of Manifest</h3>
 
-<p>Use the following example to create an <code>AndroidManifest.xml</code> file that declares the instrumentation. Specify that the framework supplied Instrumentation TestRunner targest the package of your application, allowing the tests that are run with the instrumentation to get access to all of the classes of your application without having to build the source into the test app. The name of the test application is typically the same as your target application with <code>.tests</code> appended. </p>
+<p>Use the following example to create an <code>AndroidManifest.xml</code> file that declares the instrumentation. Specify that the framework supplied InstrumentationTestRunner targets the package of your application, allowing the tests that are run with the instrumentation to get access to all of the classes of your application without having to build the source into the test app. The name of the test application is typically the same as your target application with <code>.tests</code> appended. </p>
 <pre> 
 # Add appropriate copyright banner here
 &lt;manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.samples.tests"&gt;
-    &lt;uses-permission android:name="android.permission.RUN_INSTRUMENTATION" /&gt;
+    package="com.example.android.apis.tests"&gt;
+
+    &lt;!-- We add an application tag here just so that we can indicate that
+         this package needs to link against the android.test library,
+         which is needed when building test cases. -->
+    &lt;application>
+        &lt;uses-library android:name="android.test.runner" />
+    &lt;/application>
+
     &lt;!--
     This declares that this app uses the instrumentation test runner targeting
-    the package of com.android.samples.  To run the tests use the command:
-    "adb shell am instrument -w com.android.samples.tests/android.test.InstrumentationTestRunner"
-    --&gt;
+    the package of com.example.android.apis.  To run the tests use the command:
+    "adb shell am instrument -w com.example.android.apis.tests/android.test.InstrumentationTestRunner"
+    -->
     &lt;instrumentation android:name="android.test.InstrumentationTestRunner"
-                     android:targetPackage="com.android.samples"
-                     android:label="Tests for Api Demos."/&gt;
+                     android:targetPackage="com.example.android.apis"
+                     android:label="Tests for Api Demos."/>
+
 &lt;/manifest&gt;
 </pre> 
 <p>&nbsp;</p> 
@@ -520,7 +525,7 @@ $ adb shell am instrument -w \
 </pre> 
 
 
-<a name="androidInstrumentationTestingCreatingTestRunner"></a><h3>New Instrumentation TestRunner</h3>
+<a name="androidInstrumentationTestingCreatingTestRunner"></a><h3>New InstrumentationTestRunner</h3>
 
 <p>Create a class that derives from this class. You must override two abstract methods; one that returns the class loader of the target package, and another that defines all of the tests within the package. For example, the snippet below displays the test runner for the framework tests.</p>
 <pre class="prettify">
@@ -544,8 +549,6 @@ public class FrameworkInstrumentationTestRunner extends InstrumentationTestRunne
 </pre>
 <p> Next, in an appropriate <code>AndroidManifest.xml</code>, define the instrumentation for the derived class with the appropriate <code>android:targetPackage</code> set.  For example, the snippet below defines the instrumentation runner for the framework tests.</p>
 <pre class="prettify">
-&lt;uses-permission android:name="android.permission.RUN_INSTRUMENTATION" /&gt;
-
 &lt;instrumentation android:name="android.tests.FrameworkInstrumentationTestRunner"
                  android:targetPackage="com.google.android.frameworktest"
                  android:label="framework instrumentation test runner" /&gt;
diff --git a/pdk/ndk/Android_NDK_README.html b/pdk/ndk/Android_NDK_README.html
new file mode 100644 (file)
index 0000000..d4c307b
--- /dev/null
@@ -0,0 +1,741 @@
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+
+<base target="_top">
+
+<style type="text/css">
+  
+
+/* default css */
+
+table {
+  font-size: 1em;
+  line-height: inherit;
+}
+
+
+tr {
+  
+  text-align: left;
+  
+}
+
+
+div, address, ol, ul, li, option, select {
+  margin-top: 0px;
+  margin-bottom: 0px;
+}
+
+p {
+  margin: 0px;
+}
+
+body {
+  margin: 6px;
+  padding: 0px;
+  font-family: Verdana, sans-serif;
+  font-size: 10pt;
+  background-color: #ffffff;
+}
+
+
+img {
+  -moz-force-broken-image-icon: 1;
+}
+
+@media screen {
+  html.pageview {
+    background-color: #f3f3f3 !important;
+  }
+
+  
+
+  body {
+    min-height: 1100px;
+    
+    counter-reset: __goog_page__;
+  }
+  * html body {
+    height: 1100px;
+  }
+  .pageview body {
+    border-top: 1px solid #ccc;
+    border-left: 1px solid #ccc;
+    border-right: 2px solid #bbb;
+    border-bottom: 2px solid #bbb;
+    width: 648px !important;
+    margin: 15px auto 25px;
+    padding: 40px 50px;
+  }
+  /* IE6 */
+  * html {
+    overflow-y: scroll;
+  }
+  * html.pageview body {
+    overflow-x: auto;
+  }
+  /* Prevent repaint errors when scrolling in Safari. This "Star-7" css hack
+     targets Safari 3.1, but not WebKit nightlies and presumably Safari 4.
+     That's OK because this bug is fixed in WebKit nightlies/Safari 4 :-). */
+  html*#wys_frame::before {
+    content: '\A0';
+    position: fixed;
+    overflow: hidden;
+    width: 0;
+    height: 0;
+    top: 0;
+    left: 0;
+  }
+  
+  
+
+  
+    .writely-callout-data {
+      display: none;
+      *display: inline-block;
+      *width: 0;
+      *height: 0;
+      *overflow: hidden;
+    }
+    .writely-footnote-marker {
+      background-image: url('MISSING');
+      background-color: transparent;
+      background-repeat: no-repeat;
+      width: 7px;
+      overflow: hidden;
+      height: 16px;
+      vertical-align: top;
+
+      
+      -moz-user-select: none;
+    }
+    .editor .writely-footnote-marker {
+      cursor: move;
+    }
+    .writely-footnote-marker-highlight {
+      background-position: -15px 0;
+      -moz-user-select: text;
+    }
+    .writely-footnote-hide-selection ::-moz-selection, .writely-footnote-hide-selection::-moz-selection {
+      background: transparent;
+    }
+    .writely-footnote-hide-selection ::selection, .writely-footnote-hide-selection::selection {
+      background: transparent;
+    }
+    .writely-footnote-hide-selection {
+      cursor: move;
+    }
+
+    
+    .editor .writely-comment-yellow {
+      background-color: #FF9;
+      background-position: -240px 0;
+    }
+    .editor .writely-comment-yellow-hover {
+      background-color: #FF0;
+      background-position: -224px 0;
+    }
+    .editor .writely-comment-blue {
+      background-color: #C0D3FF;
+      background-position: -16px 0;
+    }
+    .editor .writely-comment-blue-hover {
+      background-color: #6292FE;
+      background-position: 0 0;
+    }
+    .editor .writely-comment-orange {
+      background-color: #FFDEAD;
+      background-position: -80px 0;
+    }
+    .editor .writely-comment-orange-hover {
+      background-color: #F90;
+      background-position: -64px 0;
+    }
+    .editor .writely-comment-green {
+      background-color: #99FBB3;
+      background-position: -48px 0;
+    }
+    .editor .writely-comment-green-hover {
+      background-color: #00F442;
+      background-position: -32px 0;
+    }
+    .editor .writely-comment-cyan {
+      background-color: #CFF;
+      background-position: -208px 0;
+    }
+    .editor .writely-comment-cyan-hover {
+      background-color: #0FF;
+      background-position: -192px 0;
+    }
+    .editor .writely-comment-purple {
+      background-color: #EBCCFF;
+      background-position: -144px 0;
+    }
+    .editor .writely-comment-purple-hover {
+      background-color: #90F;
+      background-position: -128px 0;
+    }
+    .editor .writely-comment-magenta {
+      background-color: #FCF;
+      background-position: -112px 0;
+    }
+    .editor .writely-comment-magenta-hover {
+      background-color: #F0F;
+      background-position: -96px 0;
+    }
+    .editor .writely-comment-red {
+      background-color: #FFCACA;
+      background-position: -176px 0;
+    }
+    .editor .writely-comment-red-hover {
+      background-color: #FF7A7A;
+      background-position: -160px 0;
+    }
+
+    .editor .writely-comment-marker {
+      background-image: url('MISSING');
+      background-color: transparent;
+      padding-right: 11px;
+      background-repeat: no-repeat;
+      width: 16px;
+      height: 16px;
+      -moz-user-select: none;
+    }
+
+    .editor .writely-comment-hidden {
+      padding: 0;
+      background: none;
+    }
+    .editor .writely-comment-marker-hidden {
+      background: none;
+      padding: 0;
+      width: 0;
+    }
+    .editor .writely-comment-none {
+      opacity: .2;
+      filter:progid:DXImageTransform.Microsoft.Alpha(opacity=20);
+      -moz-opacity: .2;
+    }
+    .editor .writely-comment-none-hover {
+      opacity: .2;
+      filter:progid:DXImageTransform.Microsoft.Alpha(opacity=20);
+      -moz-opacity: .2;
+    }
+  
+
+
+  
+  .br_fix br:not(:-moz-last-node):not(:-moz-first-node) {
+    
+    position:relative;
+    
+    left: -1ex
+    
+  }
+  
+  .br_fix br+br {
+    position: static !important
+  }
+}
+
+h6 { font-size: 8pt }
+h5 { font-size: 8pt }
+h4 { font-size: 10pt }
+h3 { font-size: 12pt }
+h2 { font-size: 14pt }
+h1 { font-size: 18pt }
+
+blockquote {padding: 10px; border: 1px #DDD dashed }
+
+a img {border: 0}
+
+.pb {
+  border-width: 0;
+  page-break-after: always;
+  /* We don't want this to be resizeable, so enforce a width and height
+     using !important */
+  height: 1px !important;
+  width: 100% !important;
+}
+
+.editor .pb {
+  border-top: 1px dashed #C0C0C0;
+  border-bottom: 1px dashed #C0C0C0;
+}
+
+div.google_header, div.google_footer {
+  position: relative;
+  margin-top: 1em;
+  margin-bottom: 1em;
+}
+
+
+/* Table of contents */
+.editor div.writely-toc {
+  background-color: #f3f3f3;
+  border: 1px solid #ccc;
+}
+.writely-toc > ol {
+  padding-left: 3em;
+  font-weight: bold;
+}
+ol.writely-toc-subheading {
+  padding-left: 1em;
+  font-weight: normal;
+}
+/* IE6 only */
+* html writely-toc ol {
+  list-style-position: inside;
+}
+.writely-toc-none {
+  list-style-type: none;
+}
+.writely-toc-decimal {
+  list-style-type: decimal;
+}
+.writely-toc-upper-alpha {
+  list-style-type: upper-alpha;
+}
+.writely-toc-lower-alpha {
+  list-style-type: lower-alpha;
+}
+.writely-toc-upper-roman {
+  list-style-type: upper-roman;
+}
+.writely-toc-lower-roman {
+  list-style-type: lower-roman;
+}
+.writely-toc-disc {
+  list-style-type: disc;
+}
+
+/* end default css */
+
+
+  /* default print css */
+  
+  @media print {
+    body {
+      padding: 0;
+      margin: 0;
+    }
+
+    div.google_header, div.google_footer {
+      display: block;
+      min-height: 0;
+      border: none;
+    }
+
+    div.google_header {
+      flow: static(header);
+    }
+
+    /* used to insert page numbers */
+    div.google_header::before, div.google_footer::before {
+      position: absolute;
+      top: 0;
+    }
+
+    div.google_footer {
+      flow: static(footer);
+    }
+
+    /* always consider this element at the start of the doc */
+    div#google_footer {
+      flow: static(footer, start);
+    }
+
+    span.google_pagenumber {
+      content: counter(page);
+    }
+
+    span.google_pagecount {
+      content: counter(pages);
+    }
+
+
+    callout.google_footnote {
+      
+      display: prince-footnote;
+      footnote-style-position: inside;
+      /* These styles keep the footnote from taking on the style of the text
+         surrounding the footnote marker. They can be overridden in the
+         document CSS. */
+      color: #000;
+      font-family: Verdana;
+      font-size: 10.0pt;
+      font-weight: normal;
+    }
+
+    /* Table of contents */
+    #WritelyTableOfContents a::after {
+      content: leader('.') target-counter(attr(href), page);
+    }
+
+    #WritelyTableOfContents a {
+      text-decoration: none;
+      color: black;
+    }
+  }
+
+  @page {
+    @top {
+      content: flow(header);
+    }
+    @bottom {
+      content: flow(footer);
+    }
+    @footnotes {
+      border-top: solid black thin;
+      padding-top: 8pt;
+    }
+  }
+  /* end default print css */
+
+
+/* custom css */
+
+
+/* end custom css */
+
+
+
+  /* ui edited css */
+  
+  body {
+    font-family: Verdana;
+    
+    font-size: 10.0pt;
+    line-height: normal;
+    background-color: #ffffff;
+  }
+  /* end ui edited css */
+
+
+
+/* editor CSS */
+.editor a:visited {color: #551A8B}
+.editor table.zeroBorder {border: 1px dotted gray}
+.editor table.zeroBorder td {border: 1px dotted gray}
+.editor table.zeroBorder th {border: 1px dotted gray}
+
+
+.editor div.google_header, .editor div.google_footer {
+  border: 2px #DDDDDD dashed;
+  position: static;
+  width: 100%;
+  min-height: 2em;
+}
+
+.editor .misspell {background-color: yellow}
+
+.editor .writely-comment {
+  font-size: 9pt;
+  line-height: 1.4;
+  padding: 1px;
+  border: 1px dashed #C0C0C0
+}
+
+
+/* end editor CSS */
+
+</style>
+
+</head>
+
+<body onload="DoPageLoad();"
+    
+    revision="cfnx2f69_111dp3jzfgb:107">
+
+    
+    
+    
+<h1>\r
+  Using the Android Native Development Kit (NDK)\r
+</h1>\r
+version 1.3<br>\r
+<br>\r
+<h2>\r
+  Introduction\r
+</h2>\r
+The Android Native Development Kit enables developers to write shared libraries\r
+in C or C++ and call them from Java code. The native shared libraries can be\r
+packaged into apk files along with a normal Android application written in Java,\r
+so that the resulting Android application can be downloaded and installed on an\r
+Android phone.<br>\r
+<br>\r
+The Native Development Kit consists of:<br>\r
+<ul>\r
+  <li>\r
+    C/C++ headers for native APIs<br>\r
+  </li>\r
+  <li>\r
+    C/C++ libraries for native APIs<br>\r
+  </li>\r
+  <li>\r
+    Documentation\r
+  </li>\r
+  <li>\r
+    Sample Code\r
+  </li>\r
+</ul>\r
+<br>\r
+The Native Development Kit is designed to be used with the Android SDK:<br>\r
+<ul>\r
+  <li>\r
+    The NDK is used to create a shared library containing native code.\r
+  </li>\r
+  <li>\r
+    The SDK is used to create an Android application written in Java that calls\r
+    into the native code shared library.\r
+  </li>\r
+</ul>\r
+<h1>\r
+</h1>\r
+<h2>\r
+  Setting up your machine<br>\r
+</h2>\r
+The Native Development Kit may be installed on either Linux or OS X. Developing\r
+under Windows is not yet supported.<br>\r
+<div>\r
+  <h3>\r
+    Linux Installation\r
+  </h3>\r
+  The\r
+  Android&nbsp;build&nbsp;is&nbsp;routinely&nbsp;tested&nbsp;on&nbsp;recent&nbsp;versions&nbsp;of&nbsp;Ubuntu&nbsp;(6.06&nbsp;and&nbsp;later),&nbsp;but\r
+  may work on other distributions as well.<br>\r
+  <h4>\r
+    <a name=TOC-Ubuntu-Linux-i386-></a><span style=FONT-FAMILY:Verdana>Ubuntu\r
+    Linux (i386)</span>\r
+  </h4>\r
+  <div style=FONT-FAMILY:Verdana>\r
+    To set up your Linux development environment, make sure you have the\r
+    following:<span style="WORD-SPACING:0px; FONT-STYLE:normal; FONT-VARIANT:normal; FONT-WEIGHT:normal; font-size-adjust:none; font-stretch:normal; TEXT-TRANSFORM:none; COLOR:#000000; WHITE-SPACE:normal; LETTER-SPACING:normal; border-collapse:separate"><font size=2>\r
+    </font></span>\r
+  </div>\r
+  <div style="MARGIN-TOP:0px; MARGIN-BOTTOM:0px; FONT-FAMILY:Verdana">\r
+    <div style="MARGIN-TOP:0px; MARGIN-BOTTOM:0px">\r
+      <div style="MARGIN-TOP:0px; MARGIN-BOTTOM:0px">\r
+        <ul style="MARGIN-TOP:0px; MARGIN-BOTTOM:0px">\r
+          <li style="MARGIN-TOP:8px; MARGIN-BOTTOM:8px">\r
+            Git 1.5.4 or\r
+            newer<span style="FONT-WEIGHT:normal; WORD-SPACING:0px; TEXT-TRANSFORM:none; COLOR:#000000; FONT-STYLE:normal; WHITE-SPACE:normal; LETTER-SPACING:normal; border-collapse:separate; FONT-VARIANT:normal"><font size=2>.&nbsp;</font></span>\r
+          </li>\r
+        </ul>\r
+      </div>\r
+    </div>\r
+  </div>\r
+  <blockquote style="BORDER:medium none ; MARGIN:0pt 0pt 0pt 40px; PADDING:0px">\r
+    <span style=FONT-FAMILY:arial><span style="FONT-WEIGHT:normal; WORD-SPACING:0px; TEXT-TRANSFORM:none; COLOR:#000000; FONT-STYLE:normal; WHITE-SPACE:normal; LETTER-SPACING:normal; border-collapse:separate; FONT-VARIANT:normal"><span style="FONT-FAMILY:courier new,monospace">$\r
+    </span></span><span style="FONT-FAMILY:courier new,monospace">sudo apt-get\r
+    install git-core<br>\r
+    </span></span>\r
+  </blockquote>\r
+  <div>\r
+    <div style="MARGIN-TOP:0px; MARGIN-BOTTOM:0px">\r
+      <div style="MARGIN-TOP:0px; MARGIN-BOTTOM:0px; FONT-FAMILY:arial,sans-serif">\r
+        <div style="MARGIN-TOP:0px; MARGIN-BOTTOM:0px">\r
+          <h4>\r
+            <a name=TOC-Ubuntu-Linux-amd64-></a><span style=FONT-FAMILY:Verdana>Ubuntu\r
+            Linux (amd64)</span>\r
+          </h4>\r
+          <span style=FONT-FAMILY:Verdana>This has not been as well\r
+          tested.</span>\r
+        </div>\r
+        <div style="MARGIN-TOP:0px; MARGIN-BOTTOM:0px; FONT-FAMILY:Verdana">\r
+          <br>\r
+        </div>\r
+        <div style="MARGIN-TOP:0px; MARGIN-BOTTOM:0px; FONT-FAMILY:Verdana">\r
+          The Android build requires a 32-bit build environment:\r
+        </div>\r
+        <div style="MARGIN-TOP:0px; MARGIN-BOTTOM:0px; FONT-FAMILY:Verdana">\r
+          <ul>\r
+            <li>\r
+              Get the packages as listed above in the i386\r
+              instructions:<span style="FONT-WEIGHT:normal; WORD-SPACING:0px; TEXT-TRANSFORM:none; COLOR:#000000; FONT-STYLE:normal; WHITE-SPACE:normal; LETTER-SPACING:normal; border-collapse:separate; FONT-VARIANT:normal">&nbsp;&nbsp;&nbsp;</span>\r
+            </li>\r
+          </ul>\r
+        </div>\r
+      </div>\r
+    </div>\r
+  </div>\r
+  <blockquote style="BORDER:medium none ; MARGIN:0pt 0pt 0pt 40px; PADDING:0px">\r
+    <span style=FONT-FAMILY:arial><span style="FONT-WEIGHT:normal; WORD-SPACING:0px; TEXT-TRANSFORM:none; COLOR:#000000; FONT-STYLE:normal; WHITE-SPACE:normal; LETTER-SPACING:normal; border-collapse:separate; FONT-VARIANT:normal"><span style="FONT-FAMILY:courier new,monospace">$&nbsp;</span></span><span style="FONT-FAMILY:courier new,monospace">sudo\r
+    apt-get install git-core<br>\r
+    </span></span>\r
+  </blockquote>\r
+  <h4>\r
+    <a name=TOC-Other-Linux></a>Other Linux\r
+  </h4>\r
+  <p>\r
+    There's\r
+    no&nbsp;reason&nbsp;why&nbsp;Android&nbsp;cannot&nbsp;be&nbsp;built&nbsp;on&nbsp;non-Ubuntu&nbsp;systems<span style=FONT-WEIGHT:normal><font size=2>.&nbsp;In&nbsp;general&nbsp;you&nbsp;will&nbsp;need:</font></span>\r
+  </p>\r
+  <ul>\r
+    <li>\r
+      Git&nbsp;1.5.4&nbsp;or&nbsp;newer.&nbsp;You&nbsp;can&nbsp;find&nbsp;it&nbsp;at&nbsp;<a href=http://git.or.cz/ rel=nofollow>http://git.or.cz/</a><span style=FONT-FAMILY:arial></span>\r
+    </li>\r
+  </ul>\r
+  <div>\r
+    <h3>\r
+      Mac OS Installation\r
+    </h3>\r
+    <ul>\r
+      <li>\r
+        <span style=FONT-FAMILY:arial,sans-serif>To build the Android files in a\r
+        Mac OS environment, you need an Intel/x86 machine. The Android build\r
+        system and tools do not support the older PowerPC architecture.</span>\r
+      </li>\r
+      <li>\r
+        <span style=FONT-FAMILY:arial,sans-serif>Android must be built on a\r
+        case-sensitive file system.<br>\r
+        </span>\r
+      </li>\r
+      <ul>\r
+        <li>\r
+          We recommend that you build Android on a partition that has been\r
+          formatted with the "Case-sensitive Journaled HFS+" file system:\r
+        </li>\r
+        <ul>\r
+          <li>\r
+            A case-sensitive file system is required because the sources contain\r
+            files that differ only in case.\r
+          </li>\r
+          <li>\r
+            Journaled systems are more robust. (This is optional, but\r
+            recommended.)\r
+          </li>\r
+          <li>\r
+            HFS+ is required to successfully build Mac OS applications such as\r
+            the Android Emulator for OS X.\r
+          </li>\r
+        </ul>\r
+        <li>\r
+          If you want to avoid partitioning/formatting your hard drive, you can\r
+          use a case-sensitive disk image instead.\r
+        </li>\r
+        <ul>\r
+          <li>\r
+            To create the image:<br>\r
+            <ul>\r
+              <li>\r
+                launch /Applications/Utilities/Disk Utility\r
+              </li>\r
+              <li>\r
+                select "New Image"\r
+              </li>\r
+              <li>\r
+                size: 8 GB (this will work, but you can choose more if you want\r
+                to)\r
+              </li>\r
+              <li>\r
+                volume format: case sensitive, journaled\r
+              </li>\r
+            </ul>\r
+          </li>\r
+          <li>\r
+            This will create a .dmg file which, once mounted, acts as a drive\r
+            with the required formatting for Android development. For a disk\r
+            image named "android.dmg" stored in your home directory, you can add\r
+            the following to your ~/.bash_profile to mount the image when you\r
+            execute "mountAndroid":<br>\r
+            <br>\r
+            <div style=MARGIN-LEFT:40px>\r
+              <span style="FONT-FAMILY:courier new,monospace"># command to mount\r
+              the android file\r
+              image</span><br style="FONT-FAMILY:courier new,monospace">\r
+              <span style="FONT-FAMILY:courier new,monospace">function\r
+              mountAndroid&nbsp; { hdiutil attach ~/android.dmg&nbsp;\r
+              -mountpoint /Volumes/android; }</span><br>\r
+            </div>\r
+            <br>\r
+            Once mounted, you'll do all your work in the "android" volume. You\r
+            can eject it (unmount it) just like you would with an external\r
+            drive.\r
+          </li>\r
+        </ul>\r
+      </ul>\r
+    </ul>\r
+    <div>\r
+      <br>\r
+      <ul>\r
+        <li>\r
+          Install git 1.5.4 or newer. You can find it at\r
+          <a href=http://git.or.cz/ rel=nofollow>http://git.or.cz/</a>\r
+        </li>\r
+      </ul>\r
+      <h2>\r
+        Installing the Android SDK\r
+      </h2>\r
+      The Android NDK uses the Android SDK.&nbsp;You can find the Android SDK at\r
+      <a href=http://code.google.com/android/download.html id=a.-o title=http://code.google.com/android/download.html>http://code.google.com/android/download.html</a><br>\r
+      This version of the Android NDK requires the Cupcake version of the\r
+      Android SDK.<br>\r
+      <br>\r
+      <h2>\r
+        Installing the Prebuilt Native Toolchain<br>\r
+      </h2>\r
+      The NDK uses the prebuilt native toolchain from the Android Open Source\r
+      git repository.<br>\r
+      <br>\r
+      To download the prebuilt native toolchain to your working directory,\r
+      execute the following commands:<br>\r
+      <br>\r
+      <span style="FONT-FAMILY:Courier New"></span>\r
+      <div style=MARGIN-LEFT:40px>\r
+        <span style="FONT-FAMILY:Courier New">git clone\r
+        git://android.git.kernel.org/platform/prebuilt.git</span><br>\r
+        <span style="FONT-FAMILY:Courier New">cd prebuilt</span><br>\r
+        <span style="FONT-FAMILY:Courier New">git checkout -b cupcake -t\r
+        origin/cupcake</span><br>\r
+      </div>\r
+      <div style=MARGIN-LEFT:40px>\r
+        <span style="FONT-FAMILY:Courier New"></span>\r
+      </div>\r
+      <br>\r
+      <h2>\r
+        Setting Environment Variables\r
+      </h2>\r
+      The NDK requires that you set two environment variables:<br>\r
+      <ul>\r
+        <li>\r
+          PREBUILT must be set to the directory that contains the prebuilt\r
+          toolchain. Include the "prebuilt" directory in the path. Example:\r
+          /Volumes/android/prebuilt<br>\r
+        </li>\r
+        <li>\r
+          ANDROID_SDK_BASE must be set to the directory that contains the\r
+          Android SDK. Example: ~/AndroidSDK<br>\r
+        </li>\r
+      </ul>\r
+      <br>\r
+      <h2>\r
+        <span style=FONT-FAMILY:Verdana>Unpacking the NDK</span>\r
+      </h2>\r
+      Unpack the android_ndk.tar.gz into your working directory<br>\r
+      <br>\r
+      <div style=MARGIN-LEFT:40px>\r
+        <span style="FONT-FAMILY:Courier New">tar -zxvf\r
+        android_ndk.tar.gz</span><br>\r
+      </div>\r
+      <br>\r
+      This will create a directory called ndk. It should contain a README.html\r
+      file (this file) and the following directories: config, include, lib, and\r
+      sample.<br>\r
+      <br>\r
+      Look in the "samples" directory for samples showing how to use the NDK.<br>\r
+      <br>\r
+      <br>\r
+    </div>\r
+    <br>\r
+  </div>\r
+  <br>\r
+</div>\r
+<br></body>
+</html>
\ No newline at end of file
index 32400e3..0ec3b94 100644 (file)
@@ -100,7 +100,7 @@ ndk_src_dest_dir := $(ndk_src_tree)/ndk
 bionic_src_dest_dir := $(ndk_src_dest_dir)/include/bionic
 
 # Destinations of all common files (not picked up by tree rules below)
-ndk_common_dest_files := $(ndk_common_dest_dir)/README \
+ndk_common_dest_files := $(ndk_common_dest_dir)/Android_NDK_README.html \
                $(ndk_common_dest_dir)/config/armelf.x \
                $(ndk_common_dest_dir)/config/armelflib.x \
                $(ndk_common_dest_dir)/lib/crtbegin_dynamic.o \
@@ -114,8 +114,8 @@ ndk_common_full_dest_files := \
                $(ndk_common_full_dest_dir)/lib/libstdc++.so
 
 # Install common files outside common trees
-$(ndk_common_dest_dir)/README: $(LOCAL_PATH)/README | $(ACP)
-       @echo "NDK README: from $? to $@"
+$(ndk_common_dest_dir)/Android_NDK_README.html: $(LOCAL_PATH)/Android_NDK_README.html | $(ACP)
+       @echo "NDK Android_NDK_README.html: from $? to $@"
        $(copy-file-to-target)
 
 $(ndk_common_dest_dir)/config/armelf.x: $(BUILD_SYSTEM)/armelf.x | $(ACP)
diff --git a/pdk/ndk/README b/pdk/ndk/README
deleted file mode 100644 (file)
index ce3374b..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-Using the Native Development Kit (NDK)
-version 1.2
-
-PRECONDITIONS
-
-The Native Development Kit may be installed on either Linux or OS X.
-
-The NDK must be installed on a case-sensitive file system. Linux
-file systems are always case-sensitive, but the default OS X file
-system is case-insenstive. A case-sensitive file sytem can be
-created either by partitioning your drive to include a case-sensitive
-partition or by creating a disk image that is case-sensitive.
-
-STEP 1
-Installing arm-eabi-gcc
------------------------
-
-1) Untar the android_ndk.tar.gz:
-
-  tar -zxvf android_ndk.tar.gz
-
-This will create a directory called ndk.  It should include a README file (this
-file) and the following directories: config, include, lib, sample and toolchain.
-  
-
-STEP 2
-Samples
--------
-
-Look in the "samples" directory for samples of how to use the NDK.
index 67f14e0..c106ae3 100644 (file)
             </intent-filter>
         </activity>
 
+        <activity android:name=".app.ReorderOnLaunch"
+                android:label="@string/activity_reorder">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+        
+        <activity android:name=".app.ReorderTwo" />
+        <activity android:name=".app.ReorderThree" />
+        <activity android:name=".app.ReorderFour" />
+        
         <!-- Intent Samples -->
 
         <activity android:name=".app.Intents" android:label="@string/activity_intents">
diff --git a/samples/ApiDemos/res/layout/reorder_four.xml b/samples/ApiDemos/res/layout/reorder_four.xml
new file mode 100644 (file)
index 0000000..45f13a3
--- /dev/null
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- Demonstrates using Intent.FLAG_ACTIVITY_REORDER_TO_FRONT.
+     See corresponding Java code com.example.android.apis.app.ReorderOnLaunch.java. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:padding="4dip"
+    android:gravity="center_horizontal"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent">
+
+    <TextView
+        android:layout_width="fill_parent" android:layout_height="wrap_content"
+        android:layout_weight="0"
+        android:paddingBottom="4dip"
+        android:text="@string/reorder_four_text"/>
+
+    <Button android:id="@+id/reorder_second_to_front"
+        android:layout_width="wrap_content" android:layout_height="wrap_content"
+        android:text="@string/reorder_second_to_front">
+    </Button>
+
+</LinearLayout>
diff --git a/samples/ApiDemos/res/layout/reorder_on_launch.xml b/samples/ApiDemos/res/layout/reorder_on_launch.xml
new file mode 100644 (file)
index 0000000..850a2f5
--- /dev/null
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- Demonstrates using Intent.FLAG_ACTIVITY_REORDER_TO_FRONT.
+     See corresponding Java code com.android.sdk.app.ReorderOnLaunch.java. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:padding="4dip"
+    android:gravity="center_horizontal"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent">
+
+    <TextView
+        android:layout_width="fill_parent" android:layout_height="wrap_content"
+        android:layout_weight="0"
+        android:paddingBottom="4dip"
+        android:text="@string/reorder_on_launch"/>
+
+    <Button android:id="@+id/reorder_launch_two"
+        android:layout_width="wrap_content" android:layout_height="wrap_content"
+        android:text="@string/reorder_launch_two">
+    </Button>
+
+</LinearLayout>
diff --git a/samples/ApiDemos/res/layout/reorder_three.xml b/samples/ApiDemos/res/layout/reorder_three.xml
new file mode 100644 (file)
index 0000000..30ef41b
--- /dev/null
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- Demonstrates using Intent.FLAG_ACTIVITY_REORDER_TO_FRONT.
+     See corresponding Java code com.example.android.apis.app.ReorderOnLaunch.java. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:padding="4dip"
+    android:gravity="center_horizontal"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent">
+
+    <TextView
+        android:layout_width="fill_parent" android:layout_height="wrap_content"
+        android:layout_weight="0"
+        android:paddingBottom="4dip"
+        android:text="@string/reorder_three_text"/>
+
+    <Button android:id="@+id/reorder_launch_four"
+        android:layout_width="wrap_content" android:layout_height="wrap_content"
+        android:text="@string/reorder_launch_four">
+    </Button>
+
+</LinearLayout>
diff --git a/samples/ApiDemos/res/layout/reorder_two.xml b/samples/ApiDemos/res/layout/reorder_two.xml
new file mode 100644 (file)
index 0000000..7132561
--- /dev/null
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- Demonstrates using Intent.FLAG_ACTIVITY_REORDER_TO_FRONT.
+     See corresponding Java code com.example.android.apis.app.ReorderOnLaunch.java. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:padding="4dip"
+    android:gravity="center_horizontal"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent">
+
+    <TextView
+        android:layout_width="fill_parent" android:layout_height="wrap_content"
+        android:layout_weight="0"
+        android:paddingBottom="4dip"
+        android:text="@string/reorder_two_text"/>
+
+    <Button android:id="@+id/reorder_launch_three"
+        android:layout_width="wrap_content" android:layout_height="wrap_content"
+        android:text="@string/reorder_launch_three">
+    </Button>
+
+</LinearLayout>
index 7db3e1b..603957f 100644 (file)
     <string name="custom_title_left_button">Change Left</string>
     <string name="custom_title_right_button">Change Right</string>
 
+    <string name="activity_reorder">App/Activity/Reorder Activities</string>
+    <string name="reorder_on_launch">This is the first of a sequence of four Activities.  A button on the fourth will use the Intent.FLAG_ACTIVITY_REORDER_TO_FRONT flag to bring the second of the activities to the front of the history stack. After that, proceeding back through the history should begin with the newly-frontmost second reorder activity, then the fourth, the third, and finally the first.</string>
+    <string name="reorder_launch_two">Go to the second</string>
+    <string name="reorder_two_text">This is the second in a sequence of four Activities.</string>
+    <string name="reorder_launch_three">Go to the third</string>
+    <string name="reorder_three_text">This is the third of a sequence of four Activities.</string>
+    <string name="reorder_launch_four">Go to the fourth</string>
+    <string name="reorder_four_text">This is the last in a sequence of four Activities.</string>
+    <string name="reorder_second_to_front">Bring the second in front</string>
+
     <string name="menu_from_xml_title">App/Menu/Inflate from XML</string>
     <string name="menu_from_xml_instructions_press_menu">Select a menu resource and press the menu key.</string>
     <string name="menu_from_xml_instructions_go_back">If you want to choose another menu resource, go back and re-run this activity.</string>
 
-   <string name="voice_recognition">App/Voice Recognition</string>
+    <string name="voice_recognition">App/Voice Recognition</string>
 
     <!-- ============================== -->
     <!--  app/content examples strings  -->
index df09248..df4ef7a 100644 (file)
     android:hint="@string/search_hint" 
     android:searchMode="showSearchLabelAsBadge"
     
+    android:voiceSearchMode="showVoiceSearchButton|launchRecognizer"
+    android:voiceLanguageModel="free_form"
+    android:voicePromptText="@string/search_invoke"
+
     android:searchSuggestAuthority="com.example.android.apis.SuggestionProvider"
     android:searchSuggestSelection=" ? "
 />
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/ReorderFour.java b/samples/ApiDemos/src/com/example/android/apis/app/ReorderFour.java
new file mode 100644 (file)
index 0000000..cdff538
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * 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.example.android.apis.app;
+
+import com.example.android.apis.R;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+
+public class ReorderFour extends Activity {
+    @Override
+    protected void onCreate(Bundle savedState) {
+        super.onCreate(savedState);
+        
+        setContentView(R.layout.reorder_four);
+        
+        Button twoButton = (Button) findViewById(R.id.reorder_second_to_front);
+        twoButton.setOnClickListener(mClickListener);
+    }
+
+    private final OnClickListener mClickListener = new OnClickListener() {
+        public void onClick(View v) {
+            Intent intent = new Intent(ReorderFour.this, ReorderTwo.class);
+            intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+            startActivity(intent);
+        }
+    };
+}
\ No newline at end of file
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/ReorderOnLaunch.java b/samples/ApiDemos/src/com/example/android/apis/app/ReorderOnLaunch.java
new file mode 100644 (file)
index 0000000..5856184
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * 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.example.android.apis.app;
+
+import com.example.android.apis.R;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+
+public class ReorderOnLaunch extends Activity {
+    @Override
+    protected void onCreate(Bundle savedState) {
+        super.onCreate(savedState);
+        
+        setContentView(R.layout.reorder_on_launch);
+        
+        Button twoButton = (Button) findViewById(R.id.reorder_launch_two);
+        twoButton.setOnClickListener(mClickListener);
+    }
+
+    private final OnClickListener mClickListener = new OnClickListener() {
+        public void onClick(View v) {
+            startActivity(new Intent(ReorderOnLaunch.this, ReorderTwo.class));
+        }
+    };
+}
\ No newline at end of file
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/ReorderThree.java b/samples/ApiDemos/src/com/example/android/apis/app/ReorderThree.java
new file mode 100644 (file)
index 0000000..7f725a6
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * 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.example.android.apis.app;
+
+import com.example.android.apis.R;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+
+public class ReorderThree extends Activity {
+    @Override
+    protected void onCreate(Bundle savedState) {
+        super.onCreate(savedState);
+        
+        setContentView(R.layout.reorder_three);
+        
+        Button twoButton = (Button) findViewById(R.id.reorder_launch_four);
+        twoButton.setOnClickListener(mClickListener);
+    }
+
+    private final OnClickListener mClickListener = new OnClickListener() {
+        public void onClick(View v) {
+            startActivity(new Intent(ReorderThree.this, ReorderFour.class));
+        }
+    };
+}
\ No newline at end of file
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/ReorderTwo.java b/samples/ApiDemos/src/com/example/android/apis/app/ReorderTwo.java
new file mode 100644 (file)
index 0000000..a1521d0
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * 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.example.android.apis.app;
+
+import com.example.android.apis.R;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+
+public class ReorderTwo extends Activity {
+    @Override
+    protected void onCreate(Bundle savedState) {
+        super.onCreate(savedState);
+        
+        setContentView(R.layout.reorder_two);
+        
+        Button twoButton = (Button) findViewById(R.id.reorder_launch_three);
+        twoButton.setOnClickListener(mClickListener);
+    }
+
+    private final OnClickListener mClickListener = new OnClickListener() {
+        public void onClick(View v) {
+            startActivity(new Intent(ReorderTwo.this, ReorderThree.class));
+        }
+    };
+}
\ No newline at end of file
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.samples.app;
+package com.example.android.apis.app;
 
 import android.app.Activity;
 import android.content.Intent;
index c604991..d264134 100644 (file)
@@ -24,6 +24,7 @@ import android.graphics.drawable.Drawable;
 public class ProxyDrawable extends Drawable {
     
     private Drawable mProxy;
+    private boolean mMutated;
 
     public ProxyDrawable(Drawable target) {
         mProxy = target;
@@ -88,5 +89,14 @@ public class ProxyDrawable extends Drawable {
             mProxy.setAlpha(alpha);
         }
     }
+
+    @Override
+    public Drawable mutate() {
+        if (mProxy != null && !mMutated && super.mutate() == this) {
+            mProxy.mutate();
+            mMutated = true;
+        }
+        return this;
+    }
 }
     
index 90a0235..d96b644 100644 (file)
@@ -222,15 +222,19 @@ class GTView extends SurfaceView implements SurfaceHolder.Callback {
     private float mMotionStartRotVelocity;
     private float mMotionStartTiltAngle;
     private int mMotionDirection;
-
+    
+    private boolean mPaused = true;
+    private boolean mHaveSurface = false;
+    private boolean mStartAnimating = false;
+    
     public void surfaceCreated(SurfaceHolder holder) {
-        EGL10 egl = (EGL10)EGLContext.getEGL();
-        mEGLSurface = egl.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, this, null);
-        egl.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext);
+        mHaveSurface = true;
+        startEGL();
     }
 
     public void surfaceDestroyed(SurfaceHolder holder) {
-        // nothing to do
+        mHaveSurface = false;
+        stopEGL();
     }
 
     public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
@@ -249,59 +253,114 @@ class GTView extends SurfaceView implements SurfaceHolder.Callback {
         getHolder().addCallback(this);
         getHolder().setType(SurfaceHolder.SURFACE_TYPE_GPU);
 
-        AssetManager am = context.getAssets();
         startTime = System.currentTimeMillis();
 
-        EGL10 egl = (EGL10)EGLContext.getEGL();
-        EGLDisplay dpy = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
-        int[] version = new int[2];
-        egl.eglInitialize(dpy, version);
-        int[] configSpec = {
-                EGL10.EGL_DEPTH_SIZE,   16,
-                EGL10.EGL_NONE
-        };
-        EGLConfig[] configs = new EGLConfig[1];
-        int[] num_config = new int[1];
-        egl.eglChooseConfig(dpy, configSpec, configs, 1, num_config);
-        mEGLConfig = configs[0];
-        mEGLContext = egl.eglCreateContext(dpy, mEGLConfig, EGL10.EGL_NO_CONTEXT, null);
-        mEGLDisplay = dpy;
-
         mClock = new Clock();
 
+        startEGL();
+        
         setFocusable(true);
         setFocusableInTouchMode(true);
         requestFocus();
+    }
 
-        try {
-            loadAssets(am);
-        } catch (IOException ioe) {
-            ioe.printStackTrace();
-            throw new RuntimeException(ioe);
-        } catch (ArrayIndexOutOfBoundsException aioobe) {
-            aioobe.printStackTrace();
-            throw new RuntimeException(aioobe);
+    /**
+     * Creates an egl context. If the state of the activity is right, also
+     * creates the egl surface. Otherwise the surface will be created in a
+     * future call to createEGLSurface().
+     */
+    private void startEGL() {
+        EGL10 egl = (EGL10)EGLContext.getEGL();
+
+        if (mEGLContext == null) {
+            EGLDisplay dpy = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+            int[] version = new int[2];
+            egl.eglInitialize(dpy, version);
+            int[] configSpec = {
+                    EGL10.EGL_DEPTH_SIZE,   16,
+                    EGL10.EGL_NONE
+            };
+            EGLConfig[] configs = new EGLConfig[1];
+            int[] num_config = new int[1];
+            egl.eglChooseConfig(dpy, configSpec, configs, 1, num_config);
+            mEGLConfig = configs[0];
+
+            mEGLContext = egl.eglCreateContext(dpy, mEGLConfig, 
+                    EGL10.EGL_NO_CONTEXT, null);
+            mEGLDisplay = dpy;
+            
+            AssetManager am = mContext.getAssets();
+            try {
+                loadAssets(am);
+            } catch (IOException ioe) {
+                ioe.printStackTrace();
+                throw new RuntimeException(ioe);
+            } catch (ArrayIndexOutOfBoundsException aioobe) {
+                aioobe.printStackTrace();
+                throw new RuntimeException(aioobe);
+            }
+        }
+        
+        if (mEGLSurface == null && !mPaused && mHaveSurface) {
+            mEGLSurface = egl.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, 
+                    this, null);
+            egl.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, 
+                    mEGLContext);
+            mInitialized = false;
+            if (mStartAnimating) {
+                startAnimating();
+                mStartAnimating = false;
+            }
         }
     }
-
+    
     /**
-     * Destroy the view.
+     * Destroys the egl context. If an egl surface has been created, it is
+     * destroyed as well.
      */
-    public void destroy() {
+    private void stopEGL() {
         EGL10 egl = (EGL10)EGLContext.getEGL();
-        egl.eglMakeCurrent(mEGLDisplay, 
-                egl.EGL_NO_SURFACE, egl.EGL_NO_SURFACE, egl.EGL_NO_CONTEXT);
-        egl.eglDestroyContext(mEGLDisplay, mEGLContext);
-        egl.eglDestroySurface(mEGLDisplay, mEGLSurface);
-        egl.eglTerminate(mEGLDisplay);
-        mEGLContext = null;
+        if (mEGLSurface != null) {
+            egl.eglMakeCurrent(mEGLDisplay, 
+                    egl.EGL_NO_SURFACE, egl.EGL_NO_SURFACE, egl.EGL_NO_CONTEXT);
+            egl.eglDestroySurface(mEGLDisplay, mEGLSurface);
+            mEGLSurface = null;
+        }
+
+        if (mEGLContext != null) {
+            egl.eglDestroyContext(mEGLDisplay, mEGLContext);
+            egl.eglTerminate(mEGLDisplay);
+            mEGLContext = null;
+            mEGLDisplay = null;
+            mEGLConfig = null;
+        }
+    }
+    
+    public void onPause() {
+        mPaused = true;
+        stopAnimating();
+        stopEGL();
+    }
+    
+    public void onResume() {
+        mPaused = false;
+        startEGL();
+    }
+    
+    public void destroy() {
+        stopAnimating();
+        stopEGL();
     }
 
     /**
      * Begin animation.
      */
     public void startAnimating() {
-        mHandler.sendEmptyMessage(INVALIDATE);
+        if (mEGLSurface == null) {
+            mStartAnimating = true; // will start when egl surface is created
+        } else {
+            mHandler.sendEmptyMessage(INVALIDATE);
+        }
     }
 
     /**
@@ -1390,32 +1449,25 @@ public class GlobalTime extends Activity {
 
     GTView gtView = null;
 
-    private void setGTView() {
-        if (gtView == null) {
-            gtView = new GTView(this);
-            setContentView(gtView);
-        }
-    }
-
     @Override protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
-        setGTView();
+        gtView = new GTView(this);
+        setContentView(gtView);
     }
 
     @Override protected void onResume() {
         super.onResume();
-        setGTView();
+        gtView.onResume();
         Looper.myQueue().addIdleHandler(new Idler());
     }
 
     @Override protected void onPause() {
         super.onPause();
-        gtView.stopAnimating();
+        gtView.onPause();
     }
 
     @Override protected void onStop() {
         super.onStop();
-        gtView.stopAnimating();
         gtView.destroy();
         gtView = null;
     }
index 1929ae0..b5f3bc1 100644 (file)
@@ -19,5 +19,5 @@
 -->
 
 <resources>
-    <dimen name="key_height">46px</dimen>
-</resources>
\ No newline at end of file
+    <dimen name="key_height">46dip</dimen>
+</resources>
index cdaf27a..caf615c 100644 (file)
@@ -19,7 +19,7 @@
 -->
 
 <resources>
-    <dimen name="key_height">50px</dimen>
+    <dimen name="key_height">50dip</dimen>
     <dimen name="candidate_font_height">16sp</dimen>
     <dimen name="candidate_vertical_padding">6sp</dimen>
 </resources>
\ No newline at end of file
index d26b173..cc1174a 100644 (file)
@@ -27,6 +27,7 @@ import android.view.View;
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -73,11 +74,19 @@ public class SoftKeyboard extends InputMethodService
     private String mWordSeparators;
     
     /**
-     * Helper function to generate the various keyboard layouts used by the
-     * input method.  Takes care of regenerating the layouts if the width
-     * of the input method changes.
+     * Main initialization of the input method component.  Be sure to call
+     * to super class.
+     */
+    @Override public void onCreate() {
+        super.onCreate();
+        mWordSeparators = getResources().getString(R.string.word_separators);
+    }
+    
+    /**
+     * This is the point where you can do all of your UI initialization.  It
+     * is called after creation and any configuration change.
      */
-    private void makeKeyboards() {
+    @Override public void onInitializeInterface() {
         if (mQwertyKeyboard != null) {
             // Configuration changes can happen after the keyboard gets recreated,
             // so we need to be able to re-build the keyboards if the available
@@ -92,25 +101,12 @@ public class SoftKeyboard extends InputMethodService
     }
     
     /**
-     * Main initialization of the input method component.  Be sure to call
-     * to super class.
-     */
-    @Override public void onCreate() {
-        super.onCreate();
-        makeKeyboards();
-        mWordSeparators = getResources().getString(R.string.word_separators);
-    }
-    
-    /**
      * Called by the framework when your view for creating input needs to
      * be generated.  This will be called the first time your input method
      * is displayed, and every time it needs to be re-created such as due to
      * a configuration change.
      */
     @Override public View onCreateInputView() {
-        // We call makeKeyboards() here to regenerate them if needed due to
-        // a configuration change.
-        makeKeyboards();
         mInputView = (KeyboardView) getLayoutInflater().inflate(
                 R.layout.input, null);
         mInputView.setOnKeyboardActionListener(this);
index 565cc09..0d6054f 100644 (file)
@@ -132,7 +132,7 @@ void PropertyServer::SetDefaultProperties(void)
         { "dalvik.vm.stack-trace-file", "/data/anr/traces.txt" },
         //{ "dalvik.vm.execution-mode", "int:portable" },
         { "dalvik.vm.enableassertions", "all" },    // -ea
-        { "dalvik.vm.verify-bytecode", "false" },   // -Xverify
+        { "dalvik.vm.dexopt-flags", "" },           // e.g. "v=a,o=v,m=n"
         { "dalvik.vm.deadlock-predict", "off" },    // -Xdeadlockpredict
         //{ "dalvik.vm.jniopts", "forcecopy" },       // -Xjniopts
         { "log.redirect-stdio", "false" },          // -Xlog-stdio
index 2d25704..b44231b 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * Copyright 2007 The Android Open Source Project
  *
- * Magic entries in /sys/android_power/.
+ * Magic entries in /sys/class/power_supply/.
  */
 #include "Common.h"
 
 #include <fcntl.h>
 #include <sys/ioctl.h>
 
-#if 0
-/*
- * Set of entries found in /sys/android_power.
- */
-typedef enum DeviceIndex {
-    kPowerUnknown = 0,
-
-    kPowerAutoOffTimeout,
-    kPowerBatteryLevel,
-    kPowerBatteryLevelLow,
-    kPowerBatteryLevelRaw,
-    kPowerBatteryLevelScale,
-    kPowerBatteryLowLevel,
-    kPowerBatteryShutdownLevel,
-    kPowerChargingState,
-    kPowerRequestState,
-    kPowerState,
-
-    kPowerAcquireFullWakeLock,
-    kPowerAcquirePartialWakeLock,
-    kPowerReleaseWakeLock,
-} DeviceIndex;
-#endif
-
 /*
  * Map filename to device index.
  *
@@ -47,38 +23,26 @@ static const struct {
     //DeviceIndex     idx;
     const char*     data;
 } gDeviceMap[] = {
-    { "auto_off_timeout",           //kPowerAutoOffTimeout,
-        "\n" },
-    { "battery_level",              //kPowerBatteryLevel,
-        "9\n" },
-    { "battery_level_low",          //kPowerBatteryLevelLow,
+    { "ac/online",
         "0\n" },
-    { "battery_level_raw",          //kPowerBatteryLevelRaw,
+
+    { "battery/batt_temp",
+        "281\n", },
+    { "battery/batt_vol",
+        "4170\n" },
+    { "battery/capacity",
         "100\n" },
-    { "battery_level_scale",        //kPowerBatteryLevelScale,
-        "9\n" },
-    { "battery_low_level",          //kPowerBatteryLowLevel,
-        "10\n" },
-    { "battery_shutdown_level",     //kPowerBatteryShutdownLevel,
-        "5\n", },
-    { "charging_state",             //kPowerChargingState,
-        "Maintaining\n" },
-    { "request_state",              //kPowerRequestState,
-        "wake\n" },
-    { "state",                      //kPowerState,
-        "0-1-0\n" },
-
-    { "acquire_full_wake_lock",     //kPowerAcquireFullWakeLock,
-        "\n" },
-    { "acquire_partial_wake_lock",  //kPowerAcquirePartialWakeLock,
-        "\n" },
-    { "release_wake_lock",          //kPowerReleaseWakeLock,
-        "radio-interface PowerManagerService KeyEvents\n" },
-    { "wait_for_fb_sleep",          //kSleepFileName,
-        "" },                       // this means "block forever on read"
-    { "wait_for_fb_wake",           //kWakeFileName,
-        "0" },
+    { "battery/health",
+        "Good\n" },
+    { "battery/present",
+        "0\n" },
+    { "battery/status",
+        "Full" },
+    { "battery/technology",
+        "Li-ion\n" },
 
+    { "usb/online",
+        "1\n" },
 };
 
 /*
@@ -96,7 +60,7 @@ typedef struct PowerState {
  */
 static void configureInitialState(const char* pathName, PowerState* powerState)
 {
-    const char* cp = pathName + strlen("/sys/android_power/");
+    const char* cp = pathName + strlen("/sys/class/power_supply/");
     int i;
 
     powerState->which = -1;
@@ -134,8 +98,11 @@ static ssize_t readPower(FakeDev* dev, int fd, void* buf, size_t count)
 
     wsLog("%s: read %d\n", dev->debugName, count);
 
-    if (state->which < 0 || state->which >= sizeof(gDeviceMap)/sizeof(gDeviceMap[0]))
+    if (state->which < 0 ||
+        state->which >= (int) (sizeof(gDeviceMap)/sizeof(gDeviceMap[0])))
+    {
         return 0;
+    }
 
     const char* data = gDeviceMap[state->which].data;
     size_t strLen = strlen(data);
index 75aee16..3e223d3 100644 (file)
@@ -47,10 +47,6 @@ resources.)
  * Devices we intercept.
  *
  * Needed:
- *  /sys/android_power/battery_level_scale
- *  /sys/android_power/battery_level
- *  /sys/android_power/battery_level_raw
- *  /sys/android_power/charging_state
  *  /dev/alarm
  *  radio
  */
@@ -70,7 +66,7 @@ FakedPath fakedpaths[] =
     { "/dev/input/event0",      wsOpenDevEvent },
     { "/dev/input/*",           NULL },
     { "/dev/log/*",             wsOpenDevLog },
-    { "/sys/android_power/*",   wsOpenDevPower },
+    { "/sys/class/power_supply/*", wsOpenDevPower },
     { "/sys/devices/platform/android-vibrator/enable",  wsOpenDevVibrator },
     { "/sys/qemu_trace/*",      NULL },
     { NULL,                     NULL }
index a7ceb76..68a87b7 100644 (file)
@@ -32,9 +32,9 @@ public final class AndroidLocation {
     private static final String ANDROID_SDK_VERSION = "SDK-1.0";
 
     /**
-     * VM folder inside the path returned by {@link #getFolder()}
+     * Virtual Device folder inside the path returned by {@link #getFolder()}
      */
-    public static final String FOLDER_VMS = "vm";
+    public static final String FOLDER_AVD = "avd";
 
     /**
      * Throw when the location of the android folder couldn't be found.
@@ -56,7 +56,7 @@ public final class AndroidLocation {
      */
     public final static String getFolder() throws AndroidLocationException {
         if (sPrefsLocation == null) {
-            String home = findValidPath("user.home", "HOME");
+            String home = findValidPath("ANDROID_SDK_HOME", "user.home", "HOME");
             
             // if the above failed, we throw an exception.
             if (home == null) {
index 9a4d24b..8616cda 100644 (file)
@@ -319,7 +319,7 @@ public final class ApkBuilder {
      * @param files 
      * @param javaResources 
      * @param storeType the optional type of the debug keystore. If <code>null</code>, the default
-     * keystore type of the VM is used.
+     * keystore type of the Java VM is used.
      */
     private void createPackage(File outFile, ArrayList<FileInputStream> zipArchives,
             ArrayList<File> files, ArrayList<ApkFile> javaResources,
index 154bfa1..42022fe 100644 (file)
@@ -268,55 +268,60 @@ final class AdbHelper {
         };
         byte[] reply;
 
-        SocketChannel adbChan = SocketChannel.open(adbSockAddr);
-        adbChan.configureBlocking(false);
-
-        // if the device is not -1, then we first tell adb we're looking to talk
-        // to a specific device
-        setDevice(adbChan, device);
-
-        if (write(adbChan, request) == false)
-            throw new IOException("failed asking for frame buffer");
-
-        AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
-        if (!resp.ioSuccess || !resp.okay) {
-            Log.w("ddms", "Got timeout or unhappy response from ADB fb req: "
-                    + resp.message);
-            adbChan.close();
-            return null;
-        }
-
-        reply = new byte[16];
-        if (read(adbChan, reply) == false) {
-            Log.w("ddms", "got partial reply from ADB fb:");
-            Log.hexDump("ddms", LogLevel.WARN, reply, 0, reply.length);
-            adbChan.close();
-            return null;
-        }
-        ByteBuffer buf = ByteBuffer.wrap(reply);
-        buf.order(ByteOrder.LITTLE_ENDIAN);
-
-        imageParams.bpp = buf.getInt();
-        imageParams.size = buf.getInt();
-        imageParams.width = buf.getInt();
-        imageParams.height = buf.getInt();
-
-        Log.d("ddms", "image params: bpp=" + imageParams.bpp + ", size="
-                + imageParams.size + ", width=" + imageParams.width
-                + ", height=" + imageParams.height);
-
-        if (write(adbChan, nudge) == false)
-            throw new IOException("failed nudging");
-
-        reply = new byte[imageParams.size];
-        if (read(adbChan, reply) == false) {
-            Log.w("ddms", "got truncated reply from ADB fb data");
-            adbChan.close();
-            return null;
+        SocketChannel adbChan = null;
+        try {
+            adbChan = SocketChannel.open(adbSockAddr);
+            adbChan.configureBlocking(false);
+    
+            // if the device is not -1, then we first tell adb we're looking to talk
+            // to a specific device
+            setDevice(adbChan, device);
+    
+            if (write(adbChan, request) == false)
+                throw new IOException("failed asking for frame buffer");
+    
+            AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
+            if (!resp.ioSuccess || !resp.okay) {
+                Log.w("ddms", "Got timeout or unhappy response from ADB fb req: "
+                        + resp.message);
+                adbChan.close();
+                return null;
+            }
+    
+            reply = new byte[16];
+            if (read(adbChan, reply) == false) {
+                Log.w("ddms", "got partial reply from ADB fb:");
+                Log.hexDump("ddms", LogLevel.WARN, reply, 0, reply.length);
+                adbChan.close();
+                return null;
+            }
+            ByteBuffer buf = ByteBuffer.wrap(reply);
+            buf.order(ByteOrder.LITTLE_ENDIAN);
+    
+            imageParams.bpp = buf.getInt();
+            imageParams.size = buf.getInt();
+            imageParams.width = buf.getInt();
+            imageParams.height = buf.getInt();
+    
+            Log.d("ddms", "image params: bpp=" + imageParams.bpp + ", size="
+                    + imageParams.size + ", width=" + imageParams.width
+                    + ", height=" + imageParams.height);
+    
+            if (write(adbChan, nudge) == false)
+                throw new IOException("failed nudging");
+    
+            reply = new byte[imageParams.size];
+            if (read(adbChan, reply) == false) {
+                Log.w("ddms", "got truncated reply from ADB fb data");
+                adbChan.close();
+                return null;
+            }
+            imageParams.data = reply;
+        } finally {
+            if (adbChan != null) {
+                adbChan.close();
+            }
         }
-        imageParams.data = reply;
-
-        adbChan.close();
 
         return imageParams;
     }
@@ -330,58 +335,61 @@ final class AdbHelper {
             throws IOException {
         Log.v("ddms", "execute: running " + command);
 
-        SocketChannel adbChan = SocketChannel.open(adbSockAddr);
-        adbChan.configureBlocking(false);
-
-        // if the device is not -1, then we first tell adb we're looking to talk
-        // to a specific device
-        setDevice(adbChan, device);
-
-        byte[] request = formAdbRequest("shell:" + command); //$NON-NLS-1$
-        if (write(adbChan, request) == false)
-            throw new IOException("failed submitting shell command");
+        SocketChannel adbChan = null;
+        try {
+            adbChan = SocketChannel.open(adbSockAddr);
+            adbChan.configureBlocking(false);
 
-        AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
-        if (!resp.ioSuccess || !resp.okay) {
-            Log.e("ddms", "ADB rejected shell command (" + command + "): "
-                    + resp.message);
-            throw new IOException("sad result from adb: " + resp.message);
-        }
+            // if the device is not -1, then we first tell adb we're looking to
+            // talk
+            // to a specific device
+            setDevice(adbChan, device);
 
-        byte[] data = new byte[16384];
-        ByteBuffer buf = ByteBuffer.wrap(data);
-        while (true) {
-            int count;
+            byte[] request = formAdbRequest("shell:" + command); //$NON-NLS-1$
+            if (write(adbChan, request) == false)
+                throw new IOException("failed submitting shell command");
 
-            if (rcvr != null && rcvr.isCancelled()) {
-                Log.v("ddms", "execute: cancelled");
-                break;
+            AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
+            if (!resp.ioSuccess || !resp.okay) {
+                Log.e("ddms", "ADB rejected shell command (" + command + "): " + resp.message);
+                throw new IOException("sad result from adb: " + resp.message);
             }
 
-            count = adbChan.read(buf);
-            if (count < 0) {
-                // we're at the end, we flush the output
-                rcvr.flush();
-                Log.v("ddms",
-                        "execute '" + command + "' on '" + device + "' : EOF hit. Read: " + count);
-                break;
-            } else if (count == 0) {
-                try {
-                    Thread.sleep(WAIT_TIME * 5);
-                } catch (InterruptedException ie) {
+            byte[] data = new byte[16384];
+            ByteBuffer buf = ByteBuffer.wrap(data);
+            while (true) {
+                int count;
+
+                if (rcvr != null && rcvr.isCancelled()) {
+                    Log.v("ddms", "execute: cancelled");
+                    break;
                 }
-            } else {
-                if (rcvr != null) {
-                    rcvr.addOutput(buf.array(), buf.arrayOffset(), buf
-                            .position());
+
+                count = adbChan.read(buf);
+                if (count < 0) {
+                    // we're at the end, we flush the output
+                    rcvr.flush();
+                    Log.v("ddms", "execute '" + command + "' on '" + device + "' : EOF hit. Read: "
+                            + count);
+                    break;
+                } else if (count == 0) {
+                    try {
+                        Thread.sleep(WAIT_TIME * 5);
+                    } catch (InterruptedException ie) {
+                    }
+                } else {
+                    if (rcvr != null) {
+                        rcvr.addOutput(buf.array(), buf.arrayOffset(), buf.position());
+                    }
+                    buf.rewind();
                 }
-                buf.rewind();
             }
+        } finally {
+            if (adbChan != null) {
+                adbChan.close();
+            }
+            Log.v("ddms", "execute: returning");
         }
-
-        adbChan.close();
-
-        Log.v("ddms", "execute: returning");
     }
 
     /**
@@ -407,49 +415,55 @@ final class AdbHelper {
      */
     public static void runLogService(InetSocketAddress adbSockAddr, Device device, String logName,
             LogReceiver rcvr) throws IOException {
-        SocketChannel adbChan = SocketChannel.open(adbSockAddr);
-        adbChan.configureBlocking(false);
-
-        // if the device is not -1, then we first tell adb we're looking to talk
-        // to a specific device
-        setDevice(adbChan, device);
-
-        byte[] request = formAdbRequest("log:" + logName);
-        if (write(adbChan, request) == false) {
-            throw new IOException("failed to submit the log command");
-        }
-
-        AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
-        if (!resp.ioSuccess || !resp.okay) {
-            throw new IOException("Device rejected log command: " + resp.message);
-        }
-
-        byte[] data = new byte[16384];
-        ByteBuffer buf = ByteBuffer.wrap(data);
-        while (true) {
-            int count;
-
-            if (rcvr != null && rcvr.isCancelled()) {
-                break;
+        SocketChannel adbChan = null;
+        
+        try {
+            adbChan = SocketChannel.open(adbSockAddr);
+            adbChan.configureBlocking(false);
+    
+            // if the device is not -1, then we first tell adb we're looking to talk
+            // to a specific device
+            setDevice(adbChan, device);
+    
+            byte[] request = formAdbRequest("log:" + logName);
+            if (write(adbChan, request) == false) {
+                throw new IOException("failed to submit the log command");
             }
-
-            count = adbChan.read(buf);
-            if (count < 0) {
-                break;
-            } else if (count == 0) {
-                try {
-                    Thread.sleep(WAIT_TIME * 5);
-                } catch (InterruptedException ie) {
+    
+            AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
+            if (!resp.ioSuccess || !resp.okay) {
+                throw new IOException("Device rejected log command: " + resp.message);
+            }
+    
+            byte[] data = new byte[16384];
+            ByteBuffer buf = ByteBuffer.wrap(data);
+            while (true) {
+                int count;
+    
+                if (rcvr != null && rcvr.isCancelled()) {
+                    break;
                 }
-            } else {
-                if (rcvr != null) {
-                    rcvr.parseNewData(buf.array(), buf.arrayOffset(), buf.position());
+    
+                count = adbChan.read(buf);
+                if (count < 0) {
+                    break;
+                } else if (count == 0) {
+                    try {
+                        Thread.sleep(WAIT_TIME * 5);
+                    } catch (InterruptedException ie) {
+                    }
+                } else {
+                    if (rcvr != null) {
+                        rcvr.parseNewData(buf.array(), buf.arrayOffset(), buf.position());
+                    }
+                    buf.rewind();
                 }
-                buf.rewind();
+            }
+        } finally {
+            if (adbChan != null) {
+                adbChan.close();
             }
         }
-
-        adbChan.close();
     }
     
     /**
@@ -464,24 +478,29 @@ final class AdbHelper {
     public static boolean createForward(InetSocketAddress adbSockAddr, Device device, int localPort,
             int remotePort) throws IOException {
 
-        SocketChannel adbChan = SocketChannel.open(adbSockAddr);
-        adbChan.configureBlocking(false);
-
-        byte[] request = formAdbRequest(String.format(
-                "host-serial:%1$s:forward:tcp:%2$d;tcp:%3$d", //$NON-NLS-1$
-                device.serialNumber, localPort, remotePort));
-
-        if (write(adbChan, request) == false) {
-            throw new IOException("failed to submit the forward command.");
-        }
-        
-        AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
-        if (!resp.ioSuccess || !resp.okay) {
-            throw new IOException("Device rejected command: " + resp.message);
+        SocketChannel adbChan = null;
+        try {
+            adbChan = SocketChannel.open(adbSockAddr);
+            adbChan.configureBlocking(false);
+    
+            byte[] request = formAdbRequest(String.format(
+                    "host-serial:%1$s:forward:tcp:%2$d;tcp:%3$d", //$NON-NLS-1$
+                    device.serialNumber, localPort, remotePort));
+    
+            if (write(adbChan, request) == false) {
+                throw new IOException("failed to submit the forward command.");
+            }
+            
+            AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
+            if (!resp.ioSuccess || !resp.okay) {
+                throw new IOException("Device rejected command: " + resp.message);
+            }
+        } finally {
+            if (adbChan != null) {
+                adbChan.close();
+            }
         }
         
-        adbChan.close();
-        
         return true;
     }
 
@@ -497,24 +516,29 @@ final class AdbHelper {
     public static boolean removeForward(InetSocketAddress adbSockAddr, Device device, int localPort,
             int remotePort) throws IOException {
 
-        SocketChannel adbChan = SocketChannel.open(adbSockAddr);
-        adbChan.configureBlocking(false);
-
-        byte[] request = formAdbRequest(String.format(
-                "host-serial:%1$s:killforward:tcp:%2$d;tcp:%3$d", //$NON-NLS-1$
-                device.serialNumber, localPort, remotePort));
-
-        if (!write(adbChan, request)) {
-            throw new IOException("failed to submit the remove forward command.");
-        }
-
-        AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
-        if (!resp.ioSuccess || !resp.okay) {
-            throw new IOException("Device rejected command: " + resp.message);
+        SocketChannel adbChan = null;
+        try {
+            adbChan = SocketChannel.open(adbSockAddr);
+            adbChan.configureBlocking(false);
+    
+            byte[] request = formAdbRequest(String.format(
+                    "host-serial:%1$s:killforward:tcp:%2$d;tcp:%3$d", //$NON-NLS-1$
+                    device.serialNumber, localPort, remotePort));
+    
+            if (!write(adbChan, request)) {
+                throw new IOException("failed to submit the remove forward command.");
+            }
+    
+            AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
+            if (!resp.ioSuccess || !resp.okay) {
+                throw new IOException("Device rejected command: " + resp.message);
+            }
+        } finally {
+            if (adbChan != null) {
+                adbChan.close();
+            }
         }
 
-        adbChan.close();
-
         return true;
     }
 
index 8291f59..34ef432 100644 (file)
@@ -69,8 +69,8 @@ public final class Device implements IDevice {
     /** Serial number of the device */
     String serialNumber = null;
 
-    /** Name of the vm */
-    String mVmName = null;
+    /** Name of the AVD */
+    String mAvdName = null;
 
     /** State of the device. */
     DeviceState state = null;
@@ -94,8 +94,8 @@ public final class Device implements IDevice {
         return serialNumber;
     }
     
-    public String getVmName() {
-        return mVmName;
+    public String getAvdName() {
+        return mAvdName;
     }
 
     
index 8547ac1..f9d0fa0 100644 (file)
@@ -420,11 +420,11 @@ final class DeviceMonitor {
             device.executeShellCommand(GetPropReceiver.GETPROP_COMMAND,
                     new GetPropReceiver(device));
             
-            // now get the emulator VM name (if applicable).
+            // now get the emulator Virtual Device name (if applicable).
             if (device.isEmulator()) {
                 EmulatorConsole console = EmulatorConsole.getConsole(device);
                 if (console != null) {
-                    device.mVmName = console.getVmName();
+                    device.mAvdName = console.getAvdName();
                 }
             }
         } catch (IOException e) {
@@ -470,7 +470,7 @@ final class DeviceMonitor {
                 } catch (IOException e1) {
                     // we can ignore that one. It may already have been closed.
                 }
-                Log.e("DeviceMonitor",
+                Log.d("DeviceMonitor",
                         "Connection Failure when starting to monitor device '"
                         + device + "' : " + e.getMessage());
             }
@@ -558,7 +558,7 @@ final class DeviceMonitor {
 
                                     processIncomingJdwpData(device, socket, length);
                                 } catch (IOException ioe) {
-                                    Log.e("DeviceMonitor",
+                                    Log.d("DeviceMonitor",
                                             "Error reading jdwp list: " + ioe.getMessage());
                                     socket.close();
 
index e00073c..74c432d 100644 (file)
@@ -54,7 +54,7 @@ public final class EmulatorConsole {
     private final static String HOST = "127.0.0.1";  //$NON-NLS-1$
 
     private final static String COMMAND_PING = "help\r\n"; //$NON-NLS-1$
-    private final static String COMMAND_VM_NAME = "vm name\r\n"; //$NON-NLS-1$
+    private final static String COMMAND_AVD_NAME = "vm name\r\n"; //$NON-NLS-1$  // TODO change with emulator
     private final static String COMMAND_KILL = "kill\r\n"; //$NON-NLS-1$
     private final static String COMMAND_GSM_STATUS = "gsm status\r\n"; //$NON-NLS-1$
     private final static String COMMAND_GSM_CALL = "gsm call %1$s\r\n"; //$NON-NLS-1$
@@ -309,8 +309,8 @@ public final class EmulatorConsole {
         }
     }
     
-    public synchronized String getVmName() {
-        if (sendCommand(COMMAND_VM_NAME)) {
+    public synchronized String getAvdName() {
+        if (sendCommand(COMMAND_AVD_NAME)) {
             String[] result = readLines();
             if (result != null && result.length == 2) { // this should be the name on first line,
                                                         // and ok on 2nd line
index 61d1ca4..664b0c9 100755 (executable)
@@ -46,13 +46,13 @@ public interface IDevice {
     public String getSerialNumber();
     
     /**
-     * Returns the name of the VM the emulator is running.
+     * Returns the name of the AVD the emulator is running.
      * <p/>This is only valid if {@link #isEmulator()} returns true.
-     * <p/>If the emulator is not running any VM (for instance it's running from an Android source
+     * <p/>If the emulator is not running any AVD (for instance it's running from an Android source
      * tree build), this method will return "<code>&lt;build&gt;</code>".
-     * @return the name of the VM or <code>null</code> if there isn't any.
+     * @return the name of the AVD or <code>null</code> if there isn't any.
      */
-    public String getVmName();
+    public String getAvdName();
 
     /**
      * Returns the state of the device.
index 556fc9b..54ffae8 100644 (file)
@@ -201,7 +201,7 @@ public class RemoteAndroidTestRunnerTest extends TestCase {
             throw new UnsupportedOperationException();
         }
 
-        public String getVmName() {
+        public String getAvdName() {
             return "";
         }
 
index 1331a09..fa3f0e4 100644 (file)
@@ -200,15 +200,15 @@ public final class DevicePanel extends Panel implements IDebugBridgeChangeListen
                     case DEVICE_COL_STATE:
                         return getStateString(device);
                     case DEVICE_COL_BUILD: {
-                        String vmName = device.getVmName();
+                        String avdName = device.getAvdName();
                         String debuggable = device.getProperty(Device.PROP_DEBUGGABLE);
                         String version = device.getProperty(Device.PROP_BUILD_VERSION);
                         if (device.isEmulator()) {
                             if (debuggable != null && debuggable.equals("1")) { //$NON-NLS-1$
-                                return String.format("%1$s [%2$s, debug]", vmName, //$NON-NLS-1$
+                                return String.format("%1$s [%2$s, debug]", avdName, //$NON-NLS-1$
                                         version);
                             } else {
-                                return String.format("%1$s [%2$s]", vmName, version); //$NON-NLS-1$
+                                return String.format("%1$s [%2$s]", avdName, version); //$NON-NLS-1$
                             }
                         } else {
                             if (debuggable != null && debuggable.equals("1")) { //$NON-NLS-1$
diff --git a/tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplayFilteredLog.java b/tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplayFilteredLog.java
new file mode 100644 (file)
index 0000000..473387a
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ddmuilib.log.event;
+
+import com.android.ddmlib.log.EventContainer;
+import com.android.ddmlib.log.EventLogParser;
+
+import java.util.ArrayList;
+
+public class DisplayFilteredLog extends DisplayLog {
+
+    public DisplayFilteredLog(String name) {
+        super(name);
+    }
+
+    /**
+     * Adds event to the display.
+     */
+    @Override
+    void newEvent(EventContainer event, EventLogParser logParser) {
+        ArrayList<ValueDisplayDescriptor> valueDescriptors =
+                new ArrayList<ValueDisplayDescriptor>();
+
+        ArrayList<OccurrenceDisplayDescriptor> occurrenceDescriptors =
+                new ArrayList<OccurrenceDisplayDescriptor>();
+
+        if (filterEvent(event, valueDescriptors, occurrenceDescriptors)) {
+            addToLog(event, logParser, valueDescriptors, occurrenceDescriptors);
+        }
+    }
+
+    /**
+     * Gets display type
+     *
+     * @return display type as an integer
+     */
+    @Override
+    int getDisplayType() {
+        return DISPLAY_TYPE_FILTERED_LOG;
+    }
+}
diff --git a/tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplayGraph.java b/tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplayGraph.java
new file mode 100644 (file)
index 0000000..0cffd7e
--- /dev/null
@@ -0,0 +1,422 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ddmuilib.log.event;
+
+import com.android.ddmlib.log.EventContainer;
+import com.android.ddmlib.log.EventLogParser;
+import com.android.ddmlib.log.EventValueDescription;
+import com.android.ddmlib.log.InvalidTypeException;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.jfree.chart.axis.AxisLocation;
+import org.jfree.chart.axis.NumberAxis;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.chart.renderer.xy.AbstractXYItemRenderer;
+import org.jfree.chart.renderer.xy.XYAreaRenderer;
+import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
+import org.jfree.data.time.Millisecond;
+import org.jfree.data.time.TimeSeries;
+import org.jfree.data.time.TimeSeriesCollection;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+public class DisplayGraph extends EventDisplay {
+
+    public DisplayGraph(String name) {
+        super(name);
+    }
+
+    /**
+     * Resets the display.
+     */
+    @Override
+    void resetUI() {
+        Collection<TimeSeriesCollection> datasets = mValueTypeDataSetMap.values();
+        for (TimeSeriesCollection dataset : datasets) {
+            dataset.removeAllSeries();
+        }
+        if (mOccurrenceDataSet != null) {
+            mOccurrenceDataSet.removeAllSeries();
+        }
+        mValueDescriptorSeriesMap.clear();
+        mOcurrenceDescriptorSeriesMap.clear();
+    }
+
+    /**
+     * Creates the UI for the event display.
+     * @param parent the parent composite.
+     * @param logParser the current log parser.
+     * @return the created control (which may have children).
+     */
+    @Override
+    public Control createComposite(final Composite parent, EventLogParser logParser,
+            final ILogColumnListener listener) {
+        String title = getChartTitle(logParser);
+        return createCompositeChart(parent, logParser, title);
+    }
+
+    /**
+     * Adds event to the display.
+     */
+    @Override
+    void newEvent(EventContainer event, EventLogParser logParser) {
+        ArrayList<ValueDisplayDescriptor> valueDescriptors =
+                new ArrayList<ValueDisplayDescriptor>();
+
+        ArrayList<OccurrenceDisplayDescriptor> occurrenceDescriptors =
+                new ArrayList<OccurrenceDisplayDescriptor>();
+
+        if (filterEvent(event, valueDescriptors, occurrenceDescriptors)) {
+            updateChart(event, logParser, valueDescriptors, occurrenceDescriptors);
+        }
+    }
+
+     /**
+     * Updates the chart with the {@link EventContainer} by adding the values/occurrences defined
+     * by the {@link ValueDisplayDescriptor} and {@link OccurrenceDisplayDescriptor} objects from
+     * the two lists.
+     * <p/>This method is only called when at least one of the descriptor list is non empty.
+     * @param event
+     * @param logParser
+     * @param valueDescriptors
+     * @param occurrenceDescriptors
+     */
+    private void updateChart(EventContainer event, EventLogParser logParser,
+            ArrayList<ValueDisplayDescriptor> valueDescriptors,
+            ArrayList<OccurrenceDisplayDescriptor> occurrenceDescriptors) {
+        Map<Integer, String> tagMap = logParser.getTagMap();
+
+        Millisecond millisecondTime = null;
+        long msec = -1;
+
+        // If the event container is a cpu container (tag == 2721), and there is no descriptor
+        // for the total CPU load, then we do accumulate all the values.
+        boolean accumulateValues = false;
+        double accumulatedValue = 0;
+
+        if (event.mTag == 2721) {
+            accumulateValues = true;
+            for (ValueDisplayDescriptor descriptor : valueDescriptors) {
+                accumulateValues &= (descriptor.valueIndex != 0);
+            }
+        }
+
+        for (ValueDisplayDescriptor descriptor : valueDescriptors) {
+            try {
+                // get the hashmap for this descriptor
+                HashMap<Integer, TimeSeries> map = mValueDescriptorSeriesMap.get(descriptor);
+
+                // if it's not there yet, we create it.
+                if (map == null) {
+                    map = new HashMap<Integer, TimeSeries>();
+                    mValueDescriptorSeriesMap.put(descriptor, map);
+                }
+
+                // get the TimeSeries for this pid
+                TimeSeries timeSeries = map.get(event.pid);
+
+                // if it doesn't exist yet, we create it
+                if (timeSeries == null) {
+                    // get the series name
+                    String seriesFullName = null;
+                    String seriesLabel = getSeriesLabel(event, descriptor);
+
+                    switch (mValueDescriptorCheck) {
+                        case EVENT_CHECK_SAME_TAG:
+                            seriesFullName = String.format("%1$s / %2$s", seriesLabel,
+                                    descriptor.valueName);
+                            break;
+                        case EVENT_CHECK_SAME_VALUE:
+                            seriesFullName = String.format("%1$s", seriesLabel);
+                            break;
+                        default:
+                            seriesFullName = String.format("%1$s / %2$s: %3$s", seriesLabel,
+                                    tagMap.get(descriptor.eventTag),
+                                    descriptor.valueName);
+                            break;
+                    }
+
+                    // get the data set for this ValueType
+                    TimeSeriesCollection dataset = getValueDataset(
+                            logParser.getEventInfoMap().get(event.mTag)[descriptor.valueIndex]
+                                                                        .getValueType(),
+                            accumulateValues);
+
+                    // create the series
+                    timeSeries = new TimeSeries(seriesFullName, Millisecond.class);
+                    if (mMaximumChartItemAge != -1) {
+                        timeSeries.setMaximumItemAge(mMaximumChartItemAge * 1000);
+                    }
+
+                    dataset.addSeries(timeSeries);
+
+                    // add it to the map.
+                    map.put(event.pid, timeSeries);
+                }
+
+                // update the timeSeries.
+
+                // get the value from the event
+                double value = event.getValueAsDouble(descriptor.valueIndex);
+
+                // accumulate the values if needed.
+                if (accumulateValues) {
+                    accumulatedValue += value;
+                    value = accumulatedValue;
+                }
+
+                // get the time
+                if (millisecondTime == null) {
+                    msec = (long)event.sec * 1000L + (event.nsec / 1000000L);
+                    millisecondTime = new Millisecond(new Date(msec));
+                }
+
+                // add the value to the time series
+                timeSeries.addOrUpdate(millisecondTime, value);
+            } catch (InvalidTypeException e) {
+                // just ignore this descriptor if there's a type mismatch
+            }
+        }
+
+        for (OccurrenceDisplayDescriptor descriptor : occurrenceDescriptors) {
+            try {
+                // get the hashmap for this descriptor
+                HashMap<Integer, TimeSeries> map = mOcurrenceDescriptorSeriesMap.get(descriptor);
+
+                // if it's not there yet, we create it.
+                if (map == null) {
+                    map = new HashMap<Integer, TimeSeries>();
+                    mOcurrenceDescriptorSeriesMap.put(descriptor, map);
+                }
+
+                // get the TimeSeries for this pid
+                TimeSeries timeSeries = map.get(event.pid);
+
+                // if it doesn't exist yet, we create it.
+                if (timeSeries == null) {
+                    String seriesLabel = getSeriesLabel(event, descriptor);
+
+                    String seriesFullName = String.format("[%1$s:%2$s]",
+                            tagMap.get(descriptor.eventTag), seriesLabel);
+
+                    timeSeries = new TimeSeries(seriesFullName, Millisecond.class);
+                    if (mMaximumChartItemAge != -1) {
+                        timeSeries.setMaximumItemAge(mMaximumChartItemAge);
+                    }
+
+                    getOccurrenceDataSet().addSeries(timeSeries);
+
+                    map.put(event.pid, timeSeries);
+                }
+
+                // update the series
+
+                // get the time
+                if (millisecondTime == null) {
+                    msec = (long)event.sec * 1000L + (event.nsec / 1000000L);
+                    millisecondTime = new Millisecond(new Date(msec));
+                }
+
+                // add the value to the time series
+                timeSeries.addOrUpdate(millisecondTime, 0); // the value is unused
+            } catch (InvalidTypeException e) {
+                // just ignore this descriptor if there's a type mismatch
+            }
+        }
+
+        // go through all the series and remove old values.
+        if (msec != -1 && mMaximumChartItemAge != -1) {
+            Collection<HashMap<Integer, TimeSeries>> pidMapValues =
+                mValueDescriptorSeriesMap.values();
+
+            for (HashMap<Integer, TimeSeries> pidMapValue : pidMapValues) {
+                Collection<TimeSeries> seriesCollection = pidMapValue.values();
+
+                for (TimeSeries timeSeries : seriesCollection) {
+                    timeSeries.removeAgedItems(msec, true);
+                }
+            }
+
+            pidMapValues = mOcurrenceDescriptorSeriesMap.values();
+            for (HashMap<Integer, TimeSeries> pidMapValue : pidMapValues) {
+                Collection<TimeSeries> seriesCollection = pidMapValue.values();
+
+                for (TimeSeries timeSeries : seriesCollection) {
+                    timeSeries.removeAgedItems(msec, true);
+                }
+            }
+        }
+    }
+
+       /**
+     * Returns a {@link TimeSeriesCollection} for a specific {@link com.android.ddmlib.log.EventValueDescription.ValueType}.
+     * If the data set is not yet created, it is first allocated and set up into the
+     * {@link org.jfree.chart.JFreeChart} object.
+     * @param type the {@link com.android.ddmlib.log.EventValueDescription.ValueType} of the data set.
+     * @param accumulateValues
+     */
+    private TimeSeriesCollection getValueDataset(EventValueDescription.ValueType type, boolean accumulateValues) {
+        TimeSeriesCollection dataset = mValueTypeDataSetMap.get(type);
+        if (dataset == null) {
+            // create the data set and store it in the map
+            dataset = new TimeSeriesCollection();
+            mValueTypeDataSetMap.put(type, dataset);
+
+            // create the renderer and configure it depending on the ValueType
+            AbstractXYItemRenderer renderer;
+            if (type == EventValueDescription.ValueType.PERCENT && accumulateValues) {
+                renderer = new XYAreaRenderer();
+            } else {
+                XYLineAndShapeRenderer r = new XYLineAndShapeRenderer();
+                r.setBaseShapesVisible(type != EventValueDescription.ValueType.PERCENT);
+
+                renderer = r;
+            }
+
+            // set both the dataset and the renderer in the plot object.
+            XYPlot xyPlot = mChart.getXYPlot();
+            xyPlot.setDataset(mDataSetCount, dataset);
+            xyPlot.setRenderer(mDataSetCount, renderer);
+
+            // put a new axis label, and configure it.
+            NumberAxis axis = new NumberAxis(type.toString());
+
+            if (type == EventValueDescription.ValueType.PERCENT) {
+                // force percent range to be (0,100) fixed.
+                axis.setAutoRange(false);
+                axis.setRange(0., 100.);
+            }
+
+            // for the index, we ignore the occurrence dataset
+            int count = mDataSetCount;
+            if (mOccurrenceDataSet != null) {
+                count--;
+            }
+
+            xyPlot.setRangeAxis(count, axis);
+            if ((count % 2) == 0) {
+                xyPlot.setRangeAxisLocation(count, AxisLocation.BOTTOM_OR_LEFT);
+            } else {
+                xyPlot.setRangeAxisLocation(count, AxisLocation.TOP_OR_RIGHT);
+            }
+
+            // now we link the dataset and the axis
+            xyPlot.mapDatasetToRangeAxis(mDataSetCount, count);
+
+            mDataSetCount++;
+        }
+
+        return dataset;
+    }
+
+    /**
+     * Return the series label for this event. This only contains the pid information.
+     * @param event the {@link EventContainer}
+     * @param descriptor the {@link OccurrenceDisplayDescriptor}
+     * @return the series label.
+     * @throws InvalidTypeException
+     */
+    private String getSeriesLabel(EventContainer event, OccurrenceDisplayDescriptor descriptor)
+            throws InvalidTypeException {
+        if (descriptor.seriesValueIndex != -1) {
+            if (descriptor.includePid == false) {
+                return event.getValueAsString(descriptor.seriesValueIndex);
+            } else {
+                return String.format("%1$s (%2$d)",
+                        event.getValueAsString(descriptor.seriesValueIndex), event.pid);
+            }
+        }
+
+        return Integer.toString(event.pid);
+    }
+
+    /**
+     * Returns the {@link TimeSeriesCollection} for the occurrence display. If the data set is not
+     * yet created, it is first allocated and set up into the {@link org.jfree.chart.JFreeChart} object.
+     */
+    private TimeSeriesCollection getOccurrenceDataSet() {
+        if (mOccurrenceDataSet == null) {
+            mOccurrenceDataSet = new TimeSeriesCollection();
+
+            XYPlot xyPlot = mChart.getXYPlot();
+            xyPlot.setDataset(mDataSetCount, mOccurrenceDataSet);
+
+            OccurrenceRenderer renderer = new OccurrenceRenderer();
+            renderer.setBaseShapesVisible(false);
+            xyPlot.setRenderer(mDataSetCount, renderer);
+
+            mDataSetCount++;
+        }
+
+        return mOccurrenceDataSet;
+    }
+
+    /**
+     * Gets display type
+     *
+     * @return display type as an integer
+     */
+    @Override
+    int getDisplayType() {
+        return DISPLAY_TYPE_GRAPH;
+    }
+
+    /**
+     * Sets the current {@link EventLogParser} object.
+     */
+    @Override
+    protected void setNewLogParser(EventLogParser logParser) {
+        if (mChart != null) {
+            mChart.setTitle(getChartTitle(logParser));
+        }
+    }
+    /**
+     * Returns a meaningful chart title based on the value of {@link #mValueDescriptorCheck}.
+     *
+     * @param logParser the logParser.
+     * @return the chart title.
+     */
+    private String getChartTitle(EventLogParser logParser) {
+        if (mValueDescriptors.size() > 0) {
+            String chartDesc = null;
+            switch (mValueDescriptorCheck) {
+                case EVENT_CHECK_SAME_TAG:
+                    if (logParser != null) {
+                        chartDesc = logParser.getTagMap().get(mValueDescriptors.get(0).eventTag);
+                    }
+                    break;
+                case EVENT_CHECK_SAME_VALUE:
+                    if (logParser != null) {
+                        chartDesc = String.format("%1$s / %2$s",
+                                logParser.getTagMap().get(mValueDescriptors.get(0).eventTag),
+                                mValueDescriptors.get(0).valueName);
+                    }
+                    break;
+            }
+
+            if (chartDesc != null) {
+                return String.format("%1$s - %2$s", mName, chartDesc);
+            }
+        }
+
+        return mName;
+    }
+}
\ No newline at end of file
diff --git a/tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplayLog.java b/tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplayLog.java
new file mode 100644 (file)
index 0000000..26296f3
--- /dev/null
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ddmuilib.log.event;
+
+import com.android.ddmlib.log.EventContainer;
+import com.android.ddmlib.log.EventLogParser;
+import com.android.ddmlib.log.EventValueDescription;
+import com.android.ddmlib.log.InvalidTypeException;
+import com.android.ddmuilib.DdmUiPreferences;
+import com.android.ddmuilib.TableHelper;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ControlAdapter;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.ScrollBar;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.swt.widgets.TableItem;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+
+public class DisplayLog extends EventDisplay {
+    public DisplayLog(String name) {
+        super(name);
+    }
+
+    private final static String PREFS_COL_DATE = "EventLogPanel.log.Col1"; //$NON-NLS-1$
+    private final static String PREFS_COL_PID = "EventLogPanel.log.Col2"; //$NON-NLS-1$
+    private final static String PREFS_COL_EVENTTAG = "EventLogPanel.log.Col3"; //$NON-NLS-1$
+    private final static String PREFS_COL_VALUENAME = "EventLogPanel.log.Col4"; //$NON-NLS-1$
+    private final static String PREFS_COL_VALUE = "EventLogPanel.log.Col5"; //$NON-NLS-1$
+    private final static String PREFS_COL_TYPE = "EventLogPanel.log.Col6"; //$NON-NLS-1$
+
+    /**
+     * Resets the display.
+     */
+    @Override
+    void resetUI() {
+        mLogTable.removeAll();
+    }
+
+    /**
+     * Adds event to the display.
+     */
+    @Override
+    void newEvent(EventContainer event, EventLogParser logParser) {
+        addToLog(event, logParser);
+    }
+
+    /**
+     * Creates the UI for the event display.
+     *
+     * @param parent    the parent composite.
+     * @param logParser the current log parser.
+     * @return the created control (which may have children).
+     */
+    @Override
+    Control createComposite(Composite parent, EventLogParser logParser, ILogColumnListener listener) {
+        return createLogUI(parent, listener);
+    }
+
+    /**
+     * Adds an {@link EventContainer} to the log.
+     *
+     * @param event     the event.
+     * @param logParser the log parser.
+     */
+    private void addToLog(EventContainer event, EventLogParser logParser) {
+        ScrollBar bar = mLogTable.getVerticalBar();
+        boolean scroll = bar.getMaximum() == bar.getSelection() + bar.getThumb();
+
+        // get the date.
+        Calendar c = Calendar.getInstance();
+        long msec = (long) event.sec * 1000L;
+        c.setTimeInMillis(msec);
+
+        // convert the time into a string
+        String date = String.format("%1$tF %1$tT", c);
+
+        String eventName = logParser.getTagMap().get(event.mTag);
+        String pidName = Integer.toString(event.pid);
+
+        // get the value description
+        EventValueDescription[] valueDescription = logParser.getEventInfoMap().get(event.mTag);
+        if (valueDescription != null) {
+            for (int i = 0; i < valueDescription.length; i++) {
+                EventValueDescription description = valueDescription[i];
+                try {
+                    String value = event.getValueAsString(i);
+
+                    logValue(date, pidName, eventName, description.getName(), value,
+                            description.getEventValueType(), description.getValueType());
+                } catch (InvalidTypeException e) {
+                    logValue(date, pidName, eventName, description.getName(), e.getMessage(),
+                            description.getEventValueType(), description.getValueType());
+                }
+            }
+
+            // scroll if needed, by showing the last item
+            if (scroll) {
+                int itemCount = mLogTable.getItemCount();
+                if (itemCount > 0) {
+                    mLogTable.showItem(mLogTable.getItem(itemCount - 1));
+                }
+            }
+        }
+    }
+
+    /**
+     * Adds an {@link EventContainer} to the log. Only add the values/occurrences defined by
+     * the list of descriptors. If an event is configured to be displayed by value and occurrence,
+     * only the values are displayed (as they mark an event occurrence anyway).
+     * <p/>This method is only called when at least one of the descriptor list is non empty.
+     *
+     * @param event
+     * @param logParser
+     * @param valueDescriptors
+     * @param occurrenceDescriptors
+     */
+    protected void addToLog(EventContainer event, EventLogParser logParser,
+            ArrayList<ValueDisplayDescriptor> valueDescriptors,
+            ArrayList<OccurrenceDisplayDescriptor> occurrenceDescriptors) {
+        ScrollBar bar = mLogTable.getVerticalBar();
+        boolean scroll = bar.getMaximum() == bar.getSelection() + bar.getThumb();
+
+        // get the date.
+        Calendar c = Calendar.getInstance();
+        long msec = (long) event.sec * 1000L;
+        c.setTimeInMillis(msec);
+
+        // convert the time into a string
+        String date = String.format("%1$tF %1$tT", c);
+
+        String eventName = logParser.getTagMap().get(event.mTag);
+        String pidName = Integer.toString(event.pid);
+
+        if (valueDescriptors.size() > 0) {
+            for (ValueDisplayDescriptor descriptor : valueDescriptors) {
+                logDescriptor(event, descriptor, date, pidName, eventName, logParser);
+            }
+        } else {
+            // we display the event. Since the StringBuilder contains the header (date, event name,
+            // pid) at this point, there isn't anything else to display.
+        }
+
+        // scroll if needed, by showing the last item
+        if (scroll) {
+            int itemCount = mLogTable.getItemCount();
+            if (itemCount > 0) {
+                mLogTable.showItem(mLogTable.getItem(itemCount - 1));
+            }
+        }
+    }
+
+
+    /**
+     * Logs a value in the ui.
+     *
+     * @param date
+     * @param pid
+     * @param event
+     * @param valueName
+     * @param value
+     * @param eventValueType
+     * @param valueType
+     */
+    private void logValue(String date, String pid, String event, String valueName,
+            String value, EventContainer.EventValueType eventValueType, EventValueDescription.ValueType valueType) {
+
+        TableItem item = new TableItem(mLogTable, SWT.NONE);
+        item.setText(0, date);
+        item.setText(1, pid);
+        item.setText(2, event);
+        item.setText(3, valueName);
+        item.setText(4, value);
+
+        String type;
+        if (valueType != EventValueDescription.ValueType.NOT_APPLICABLE) {
+            type = String.format("%1$s, %2$s", eventValueType.toString(), valueType.toString());
+        } else {
+            type = eventValueType.toString();
+        }
+
+        item.setText(5, type);
+    }
+
+    /**
+     * Logs a value from an {@link EventContainer} as defined by the {@link ValueDisplayDescriptor}.
+     *
+     * @param event      the EventContainer
+     * @param descriptor the ValueDisplayDescriptor defining which value to display.
+     * @param date       the date of the event in a string.
+     * @param pidName
+     * @param eventName
+     * @param logParser
+     */
+    private void logDescriptor(EventContainer event, ValueDisplayDescriptor descriptor,
+            String date, String pidName, String eventName, EventLogParser logParser) {
+
+        String value;
+        try {
+            value = event.getValueAsString(descriptor.valueIndex);
+        } catch (InvalidTypeException e) {
+            value = e.getMessage();
+        }
+
+        EventValueDescription[] values = logParser.getEventInfoMap().get(event.mTag);
+
+        EventValueDescription valueDescription = values[descriptor.valueIndex];
+
+        logValue(date, pidName, eventName, descriptor.valueName, value,
+                valueDescription.getEventValueType(), valueDescription.getValueType());
+    }
+
+    /**
+     * Creates the UI for a log display.
+     *
+     * @param parent   the parent {@link Composite}
+     * @param listener the {@link ILogColumnListener} to notify on column resize events.
+     * @return the top Composite of the UI.
+     */
+    private Control createLogUI(Composite parent, final ILogColumnListener listener) {
+        Composite mainComp = new Composite(parent, SWT.NONE);
+        GridLayout gl;
+        mainComp.setLayout(gl = new GridLayout(1, false));
+        gl.marginHeight = gl.marginWidth = 0;
+        mainComp.addDisposeListener(new DisposeListener() {
+            public void widgetDisposed(DisposeEvent e) {
+                mLogTable = null;
+            }
+        });
+
+        Label l = new Label(mainComp, SWT.CENTER);
+        l.setText(mName);
+        l.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+
+        mLogTable = new Table(mainComp, SWT.MULTI | SWT.FULL_SELECTION | SWT.V_SCROLL |
+                SWT.BORDER);
+        mLogTable.setLayoutData(new GridData(GridData.FILL_BOTH));
+
+        IPreferenceStore store = DdmUiPreferences.getStore();
+
+        TableColumn col = TableHelper.createTableColumn(
+                mLogTable, "Time",
+                SWT.LEFT, "0000-00-00 00:00:00", PREFS_COL_DATE, store); //$NON-NLS-1$
+        col.addControlListener(new ControlAdapter() {
+            @Override
+            public void controlResized(ControlEvent e) {
+                Object source = e.getSource();
+                if (source instanceof TableColumn) {
+                    listener.columnResized(0, (TableColumn) source);
+                }
+            }
+        });
+
+        col = TableHelper.createTableColumn(
+                mLogTable, "pid",
+                SWT.LEFT, "0000", PREFS_COL_PID, store); //$NON-NLS-1$
+        col.addControlListener(new ControlAdapter() {
+            @Override
+            public void controlResized(ControlEvent e) {
+                Object source = e.getSource();
+                if (source instanceof TableColumn) {
+                    listener.columnResized(1, (TableColumn) source);
+                }
+            }
+        });
+
+        col = TableHelper.createTableColumn(
+                mLogTable, "Event",
+                SWT.LEFT, "abcdejghijklmno", PREFS_COL_EVENTTAG, store); //$NON-NLS-1$
+        col.addControlListener(new ControlAdapter() {
+            @Override
+            public void controlResized(ControlEvent e) {
+                Object source = e.getSource();
+                if (source instanceof TableColumn) {
+                    listener.columnResized(2, (TableColumn) source);
+                }
+            }
+        });
+
+        col = TableHelper.createTableColumn(
+                mLogTable, "Name",
+                SWT.LEFT, "Process Name", PREFS_COL_VALUENAME, store); //$NON-NLS-1$
+        col.addControlListener(new ControlAdapter() {
+            @Override
+            public void controlResized(ControlEvent e) {
+                Object source = e.getSource();
+                if (source instanceof TableColumn) {
+                    listener.columnResized(3, (TableColumn) source);
+                }
+            }
+        });
+
+        col = TableHelper.createTableColumn(
+                mLogTable, "Value",
+                SWT.LEFT, "0000000", PREFS_COL_VALUE, store); //$NON-NLS-1$
+        col.addControlListener(new ControlAdapter() {
+            @Override
+            public void controlResized(ControlEvent e) {
+                Object source = e.getSource();
+                if (source instanceof TableColumn) {
+                    listener.columnResized(4, (TableColumn) source);
+                }
+            }
+        });
+
+        col = TableHelper.createTableColumn(
+                mLogTable, "Type",
+                SWT.LEFT, "long, seconds", PREFS_COL_TYPE, store); //$NON-NLS-1$
+        col.addControlListener(new ControlAdapter() {
+            @Override
+            public void controlResized(ControlEvent e) {
+                Object source = e.getSource();
+                if (source instanceof TableColumn) {
+                    listener.columnResized(5, (TableColumn) source);
+                }
+            }
+        });
+
+        mLogTable.setHeaderVisible(true);
+        mLogTable.setLinesVisible(true);
+
+        return mainComp;
+    }
+
+    /**
+     * Resizes the <code>index</code>-th column of the log {@link Table} (if applicable).
+     * <p/>
+     * This does nothing if the <code>Table</code> object is <code>null</code> (because the display
+     * type does not use a column) or if the <code>index</code>-th column is in fact the originating
+     * column passed as argument.
+     *
+     * @param index        the index of the column to resize
+     * @param sourceColumn the original column that was resize, and on which we need to sync the
+     *                     index-th column width.
+     */
+    @Override
+    void resizeColumn(int index, TableColumn sourceColumn) {
+        if (mLogTable != null) {
+            TableColumn col = mLogTable.getColumn(index);
+            if (col != sourceColumn) {
+                col.setWidth(sourceColumn.getWidth());
+            }
+        }
+    }
+
+    /**
+     * Gets display type
+     *
+     * @return display type as an integer
+     */
+    @Override
+    int getDisplayType() {
+        return DISPLAY_TYPE_LOG_ALL;
+    }
+}
diff --git a/tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplaySync.java b/tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplaySync.java
new file mode 100644 (file)
index 0000000..0057c86
--- /dev/null
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ddmuilib.log.event;
+
+import com.android.ddmlib.log.EventContainer;
+import com.android.ddmlib.log.EventLogParser;
+import com.android.ddmlib.log.InvalidTypeException;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.jfree.chart.labels.CustomXYToolTipGenerator;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.chart.renderer.xy.XYBarRenderer;
+import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
+import org.jfree.data.time.FixedMillisecond;
+import org.jfree.data.time.SimpleTimePeriod;
+import org.jfree.data.time.TimePeriodValues;
+import org.jfree.data.time.TimePeriodValuesCollection;
+import org.jfree.data.time.TimeSeries;
+import org.jfree.data.time.TimeSeriesCollection;
+import org.jfree.util.ShapeUtilities;
+
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Scanner;
+import java.util.regex.Pattern;
+
+public class DisplaySync extends EventDisplay {
+
+    // Information to graph for each authority
+    private TimePeriodValues mDatasetsSync[];
+    private List<String> mTooltipsSync[];
+    private CustomXYToolTipGenerator mTooltipGenerators[];
+    private TimeSeries mDatasetsSyncTickle[];
+
+    // Dataset of error events to graph
+    private TimeSeries mDatasetError;
+    
+    // State information while processing the event stream
+    private int mLastState; // 0 if event started, 1 if event stopped
+    private long mLastStartTime; // ms
+    private long mLastStopTime; //ms
+    private String mLastDetails;
+    private int mLastEvent; // server, poll, etc
+
+    public DisplaySync(String name) {
+        super(name);
+    }
+
+    /**
+     * Resets the display.
+     */
+    @Override
+    void resetUI() {
+        initSyncDisplay();
+    }
+
+    /**
+     * Creates the UI for the event display.
+     * @param parent the parent composite.
+     * @param logParser the current log parser.
+     * @return the created control (which may have children).
+     */
+    @Override
+    public Control createComposite(final Composite parent, EventLogParser logParser,
+            final ILogColumnListener listener) {
+        Control composite = createCompositeChart(parent, logParser, "Sync Status");
+        initSyncDisplay();
+        return composite;
+    }
+
+    /**
+     * Initialize the Plot and series data for the sync display.
+     */
+    void initSyncDisplay() {
+        XYPlot xyPlot = mChart.getXYPlot();
+
+        XYBarRenderer br = new XYBarRenderer();
+        mDatasetsSync = new TimePeriodValues[NUM_AUTHS];
+        mTooltipsSync = new List[NUM_AUTHS];
+        mTooltipGenerators = new CustomXYToolTipGenerator[NUM_AUTHS];
+        mLastDetails = "";
+
+        TimePeriodValuesCollection tpvc = new TimePeriodValuesCollection();
+        xyPlot.setDataset(tpvc);
+        xyPlot.setRenderer(0, br);
+
+        XYLineAndShapeRenderer ls = new XYLineAndShapeRenderer();
+        ls.setBaseLinesVisible(false);
+        mDatasetsSyncTickle = new TimeSeries[NUM_AUTHS];
+        TimeSeriesCollection tsc = new TimeSeriesCollection();
+        xyPlot.setDataset(1, tsc);
+        xyPlot.setRenderer(1, ls);
+
+        mDatasetError = new TimeSeries("Errors", FixedMillisecond.class);
+        xyPlot.setDataset(2, new TimeSeriesCollection(mDatasetError));
+        XYLineAndShapeRenderer errls = new XYLineAndShapeRenderer();
+        errls.setBaseLinesVisible(false);
+        errls.setSeriesPaint(0, Color.RED);
+        xyPlot.setRenderer(2, errls);
+
+        for (int i = 0; i < NUM_AUTHS; i++) {
+            br.setSeriesPaint(i, AUTH_COLORS[i]);
+            ls.setSeriesPaint(i, AUTH_COLORS[i]);
+            mDatasetsSync[i] = new TimePeriodValues(AUTH_NAMES[i]);
+            tpvc.addSeries(mDatasetsSync[i]);
+            mTooltipsSync[i] = new ArrayList<String>();
+            mTooltipGenerators[i] = new CustomXYToolTipGenerator();
+            br.setSeriesToolTipGenerator(i, mTooltipGenerators[i]);
+            mTooltipGenerators[i].addToolTipSeries(mTooltipsSync[i]);
+
+            mDatasetsSyncTickle[i] = new TimeSeries(AUTH_NAMES[i] + " tickle", FixedMillisecond.class);
+            tsc.addSeries(mDatasetsSyncTickle[i]);
+            ls.setSeriesShape(i, ShapeUtilities.createUpTriangle(2.5f));
+        }
+    }
+
+    /**
+     * Updates the display with a new event.  This is the main entry point for
+     * each event.  This method has the logic to tie together the start event,
+     * stop event, and details event into one graph item.  Note that the details
+     * can happen before or after the stop event.
+     * @param event The event
+     * @param logParser the log parser (unused)
+     */
+    @Override
+    void newEvent(EventContainer event, EventLogParser logParser) {
+        try {
+            if (event.mTag == EVENT_SYNC) {
+                int state = Integer.parseInt(event.getValueAsString(1));
+                if (state == 0) { // start
+                    mLastStartTime = (long)event.sec * 1000L + (event.nsec / 1000000L);
+                    mLastState = 0;
+                    mLastEvent = Integer.parseInt(event.getValueAsString(2));
+                    mLastDetails = "";
+                } else if (state == 1) { // stop
+                    if (mLastState == 0) {
+                        mLastStopTime = (long)event.sec * 1000L + (event.nsec / 1000000L);
+                        if (mLastStartTime == 0) {
+                            // Log starts with a stop event
+                            mLastStartTime = mLastStopTime;
+                        }
+                        addEvent(event);
+                        mLastState = 1;
+                    }
+                }
+            } else if (event.mTag == EVENT_TICKLE) {
+                int auth = getAuth(event.getValueAsString(0));
+                if (auth >= 0) {
+                    long msec = (long)event.sec * 1000L + (event.nsec / 1000000L);
+                    mDatasetsSyncTickle[auth].addOrUpdate(new FixedMillisecond(msec), -1);
+                }
+            } else if (event.mTag == EVENT_SYNC_DETAILS) {
+                int auth = getAuth(event.getValueAsString(0));
+                mLastDetails = event.getValueAsString(3);
+                if (mLastState != 0) { // Not inside event
+                    long updateTime = (long)event.sec * 1000L + (event.nsec / 1000000L);
+                    if (updateTime - mLastStopTime <= 250) {
+                        // Got details within 250ms after event, so delete and re-insert
+                        // Details later than 250ms (arbitrary) are discarded as probably
+                        // unrelated.
+                        int lastItem = mDatasetsSync[auth].getItemCount();
+                        mDatasetsSync[auth].delete(lastItem-1, lastItem-1);
+                        mTooltipsSync[auth].remove(lastItem-1);
+                        addEvent(event);
+                    }
+                }
+            }
+        } catch (InvalidTypeException e) {
+        }
+    }
+
+    /**
+     * Generate the height for an event.
+     * Height is somewhat arbitrarily the count of "things" that happened
+     * during the sync.
+     * When network traffic measurements are available, code should be modified
+     * to use that instead.
+     * @param details The details string associated with the event
+     * @return The height in arbirary units (0-100)
+     */
+    private int getHeightFromDetails(String details) {
+        if (details == null) {
+            return 1; // Arbitrary
+        }
+        int total = 0;
+        String parts[] = details.split("[a-zA-Z]");
+        for (String part : parts) {
+            if ("".equals(part)) continue;
+            total += Integer.parseInt(part);
+        }
+        if (total == 0) {
+            total = 1;
+        }
+        return total;
+    }
+
+    /**
+     * Generates the tooltips text for an event.
+     * This method decodes the cryptic details string.
+     * @param auth The authority associated with the event
+     * @param details The details string
+     * @param eventSource server, poll, etc.
+     * @return The text to display in the tooltips
+     */
+    private String getTextFromDetails(int auth, String details, int eventSource) {
+
+        StringBuffer sb = new StringBuffer();
+        sb.append(AUTH_NAMES[auth]).append(": \n");
+
+        Scanner scanner = new Scanner(details);
+        Pattern charPat = Pattern.compile("[a-zA-Z]");
+        Pattern numPat = Pattern.compile("[0-9]+");
+        while (scanner.hasNext()) {
+            String key = scanner.findInLine(charPat);
+            int val = Integer.parseInt(scanner.findInLine(numPat));
+            if (auth == GMAIL && "M".equals(key)) {
+                sb.append("messages from server: ").append(val).append("\n");
+            } else if (auth == GMAIL && "L".equals(key)) {
+                sb.append("labels from server: ").append(val).append("\n");
+            } else if (auth == GMAIL && "C".equals(key)) {
+                sb.append("check conversation requests from server: ").append(val).append("\n");
+            } else if (auth == GMAIL && "A".equals(key)) {
+                sb.append("attachments from server: ").append(val).append("\n");
+            } else if (auth == GMAIL && "U".equals(key)) {
+                sb.append("op updates from server: ").append(val).append("\n");
+            } else if (auth == GMAIL && "u".equals(key)) {
+                sb.append("op updates to server: ").append(val).append("\n");
+            } else if (auth == GMAIL && "S".equals(key)) {
+                sb.append("send/receive cycles: ").append(val).append("\n");
+            } else if ("Q".equals(key)) {
+                sb.append("queries to server: ").append(val).append("\n");
+            } else if ("E".equals(key)) {
+                sb.append("entries from server: ").append(val).append("\n");
+            } else if ("u".equals(key)) {
+                sb.append("updates from client: ").append(val).append("\n");
+            } else if ("i".equals(key)) {
+                sb.append("inserts from client: ").append(val).append("\n");
+            } else if ("d".equals(key)) {
+                sb.append("deletes from client: ").append(val).append("\n");
+            } else if ("f".equals(key)) {
+                sb.append("full sync requested\n");
+            } else if ("r".equals(key)) {
+                sb.append("partial sync unavailable\n");
+            } else if ("X".equals(key)) {
+                sb.append("hard error\n");
+            } else if ("e".equals(key)) {
+                sb.append("number of parse exceptions: ").append(val).append("\n");
+            } else if ("c".equals(key)) {
+                sb.append("number of conflicts: ").append(val).append("\n");
+            } else if ("a".equals(key)) {
+                sb.append("number of auth exceptions: ").append(val).append("\n");
+            } else if ("D".equals(key)) {
+                sb.append("too many deletions\n");
+            } else if ("R".equals(key)) {
+                sb.append("too many retries: ").append(val).append("\n");
+            } else if ("b".equals(key)) {
+                sb.append("database error\n");
+            } else if ("x".equals(key)) {
+                sb.append("soft error\n");
+            } else if ("l".equals(key)) {
+                sb.append("sync already in progress\n");
+            } else if ("I".equals(key)) {
+                sb.append("io exception\n");
+            } else if (auth == CONTACTS && "p".equals(key)) {
+                sb.append("photos uploaded from client: ").append(val).append("\n");
+            } else if (auth == CONTACTS && "P".equals(key)) {
+                sb.append("photos downloaded from server: ").append(val).append("\n");
+            } else if (auth == CALENDAR && "F".equals(key)) {
+                sb.append("server refresh\n");
+            } else if (auth == CALENDAR && "s".equals(key)) {
+                sb.append("server diffs fetched\n");
+            } else {
+                sb.append(key).append("=").append(val);
+            }
+        }
+        if (eventSource == 0) {
+            sb.append("(server)");
+        } else if (eventSource == 1) {
+            sb.append("(local)");
+        } else if (eventSource == 2) {
+            sb.append("(poll)");
+        } else if (eventSource == 3) {
+            sb.append("(user)");
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Helper to add an event to the data series.
+     * Also updates error series if appropriate (x or X in details).
+     * @param event The event
+     */
+    private void addEvent(EventContainer event) {
+        try {
+            int auth = getAuth(event.getValueAsString(0));
+            double height = getHeightFromDetails(mLastDetails);
+            height = height / (mLastStopTime - mLastStartTime + 1) * 10000;
+            if (height > 30) {
+                height = 30;
+            }
+            mDatasetsSync[auth].add(new SimpleTimePeriod(mLastStartTime, mLastStopTime), height);
+            mTooltipsSync[auth].add(getTextFromDetails(auth, mLastDetails,
+                    mLastEvent));
+            mTooltipGenerators[auth].addToolTipSeries(mTooltipsSync[auth]);
+            if (mLastDetails.indexOf('x') >= 0 || mLastDetails.indexOf('X') >= 0) {
+                long msec = (long)event.sec * 1000L + (event.nsec / 1000000L);
+                mDatasetError.addOrUpdate(new FixedMillisecond(msec), -1);
+            }
+        } catch (InvalidTypeException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * Gets display type
+     *
+     * @return display type as an integer
+     */
+    @Override
+    int getDisplayType() {
+        return DISPLAY_TYPE_SYNC;
+    }
+}
\ No newline at end of file
diff --git a/tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplaySyncHistogram.java b/tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplaySyncHistogram.java
new file mode 100644 (file)
index 0000000..3087997
--- /dev/null
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ddmuilib.log.event;
+
+import com.android.ddmlib.log.EventContainer;
+import com.android.ddmlib.log.EventLogParser;
+import com.android.ddmlib.log.InvalidTypeException;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.chart.renderer.xy.AbstractXYItemRenderer;
+import org.jfree.chart.renderer.xy.XYBarRenderer;
+import org.jfree.data.time.RegularTimePeriod;
+import org.jfree.data.time.SimpleTimePeriod;
+import org.jfree.data.time.TimePeriodValues;
+import org.jfree.data.time.TimePeriodValuesCollection;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TimeZone;
+
+public class DisplaySyncHistogram extends EventDisplay {
+
+
+    // State information while processing the event stream
+    protected int mLastState; // 0 if event started, 1 if event stopped
+    protected long mLastStartTime; // ms
+    protected long mLastStopTime; //ms
+    protected String mLastDetails;
+    protected int mLastEvent; // server, poll, etc
+
+    public DisplaySyncHistogram(String name) {
+        super(name);
+    }
+
+    /**
+     * Resets the display.
+     */
+    @Override
+    void resetUI() {
+        initSyncHistogramDisplay();
+    }
+
+    /**
+     * Creates the UI for the event display.
+     * @param parent the parent composite.
+     * @param logParser the current log parser.
+     * @return the created control (which may have children).
+     */
+    @Override
+    public Control createComposite(final Composite parent, EventLogParser logParser,
+            final ILogColumnListener listener) {
+        Control composite = createCompositeChart(parent, logParser, "Sync Histogram");
+        initSyncHistogramDisplay();
+        return composite;
+    }
+
+    // Information to graph for each authority
+    private TimePeriodValues mDatasetsSyncHist[];
+
+    /**
+     * Initializes the display.
+     */
+    private void initSyncHistogramDisplay() {
+        XYPlot xyPlot = mChart.getXYPlot();
+
+        AbstractXYItemRenderer br = new XYBarRenderer();
+        mDatasetsSyncHist = new TimePeriodValues[NUM_AUTHS+1];
+        mLastDetails = "";
+        mTimePeriodMap = new HashMap[NUM_AUTHS + 1];
+
+        TimePeriodValuesCollection tpvc = new TimePeriodValuesCollection();
+        xyPlot.setDataset(tpvc);
+        xyPlot.setRenderer(br);
+
+        for (int i = 0; i < NUM_AUTHS + 1; i++) {
+            br.setSeriesPaint(i, AUTH_COLORS[i]);
+            mDatasetsSyncHist[i] = new TimePeriodValues(AUTH_NAMES[i]);
+            tpvc.addSeries(mDatasetsSyncHist[i]);
+            mTimePeriodMap[i] = new HashMap<SimpleTimePeriod, Integer>();
+
+        }
+    }
+
+    /**
+     * Updates the display with a new event.  This is the main entry point for
+     * each event.  This method has the logic to tie together the start event,
+     * stop event, and details event into one graph item.  Note that the details
+     * can happen before or after the stop event.
+     * @param event The event
+     */
+    @Override
+    void newEvent(EventContainer event, EventLogParser logParser) {
+        try {
+            if (event.mTag == EVENT_SYNC) {
+                int state = Integer.parseInt(event.getValueAsString(1));
+                if (state == 0) { // start
+                    mLastStartTime = (long)event.sec * 1000L + (event.nsec / 1000000L);
+                    mLastState = 0;
+                    mLastEvent = Integer.parseInt(event.getValueAsString(2));
+                    mLastDetails = "";
+                } else if (state == 1) { // stop
+                    if (mLastState == 0) {
+                        mLastStopTime = (long)event.sec * 1000L + (event.nsec / 1000000L);
+                        if (mLastStartTime == 0) {
+                            // Log starts with a stop event
+                            mLastStartTime = mLastStopTime;
+                        }
+                        int auth = getAuth(event.getValueAsString(0));
+                        if (mLastDetails.indexOf('x') >= 0 || mLastDetails.indexOf('X') >= 0) {
+                            auth = ERRORS;
+                        }
+                        double delta = (mLastStopTime - mLastStartTime) * 100. / 1000 / 3600; // Percent of hour
+                        addHistEvent(event, auth, delta);
+                        mLastState = 1;
+                    }
+                }
+            } else if (event.mTag == EVENT_SYNC_DETAILS) {
+                int auth = getAuth(event.getValueAsString(0));
+                mLastDetails = event.getValueAsString(3);
+                if (mLastState != 0) { // Not inside event
+                    long updateTime = (long)event.sec * 1000L + (event.nsec / 1000000L);
+                    if (updateTime - mLastStopTime <= 250) {
+                        // Got details within 250ms after event, so delete and re-insert
+                        // Details later than 250ms (arbitrary) are discarded as probably
+                        // unrelated.
+                        //int lastItem = mDatasetsSync[auth].getItemCount();
+                        //addHistEvent(event);
+                        if (mLastDetails.indexOf('x') >= 0 || mLastDetails.indexOf('X') >= 0) {
+                            // Item turns out to be in error, so transfer time from old auth to error.
+
+                            double delta = (mLastStopTime - mLastStartTime) * 100. / 1000 / 3600; // Percent of hour
+                            addHistEvent(event, auth, -delta);
+                            addHistEvent(event, ERRORS, delta);
+                        }
+                    }
+                }
+            }
+        } catch (InvalidTypeException e) {
+        }
+    }
+
+    /**
+     * Helper to add an event to the data series.
+     * Also updates error series if appropriate (x or X in details).
+     * @param event The event
+     * @param auth
+     * @param value
+     */
+    private void addHistEvent(EventContainer event, int auth, double value) {
+        SimpleTimePeriod hour = getTimePeriod(mLastStopTime, mHistWidth);
+
+        // Loop over all datasets to do the stacking.
+        for (int i = auth; i <= ERRORS; i++) {
+            addToPeriod(mDatasetsSyncHist, i, hour, value);
+        }
+    }
+
+    Map<SimpleTimePeriod, Integer> mTimePeriodMap[];
+
+    private void addToPeriod(TimePeriodValues tpv[], int auth, SimpleTimePeriod period, double value) {
+        int index;
+        if (mTimePeriodMap[auth].containsKey(period)) {
+            index = mTimePeriodMap[auth].get(period);
+            double oldValue = tpv[auth].getValue(index).doubleValue();
+            tpv[auth].update(index, oldValue + value);
+        } else {
+            index = tpv[auth].getItemCount();
+            mTimePeriodMap[auth].put(period, index);
+            tpv[auth].add(period, value);
+        }
+    }
+
+    /**
+     * Creates a multiple-hour time period for the histogram.
+     * @param time Time in milliseconds.
+     * @param numHoursWide: should divide into a day.
+     * @return SimpleTimePeriod covering the number of hours and containing time.
+     */
+    private SimpleTimePeriod getTimePeriod(long time, long numHoursWide) {
+        Date date = new Date(time);
+        TimeZone zone = RegularTimePeriod.DEFAULT_TIME_ZONE;
+        Calendar calendar = Calendar.getInstance(zone);
+        calendar.setTime(date);
+        long hoursOfYear = calendar.get(Calendar.HOUR_OF_DAY) + calendar.get(Calendar.DAY_OF_YEAR) * 24;
+        int year = calendar.get(Calendar.YEAR);
+        hoursOfYear = (hoursOfYear / numHoursWide) * numHoursWide;
+        calendar.clear();
+        calendar.set(year, 0, 1, 0, 0); // Jan 1
+        long start = calendar.getTimeInMillis() + hoursOfYear * 3600 * 1000;
+        return new SimpleTimePeriod(start, start + numHoursWide * 3600 * 1000);
+    }
+
+    /**
+     * Gets display type
+     *
+     * @return display type as an integer
+     */
+    @Override
+    int getDisplayType() {
+        return DISPLAY_TYPE_SYNC_HIST;
+    }
+}
index e36192c..bbd3e1b 100644 (file)
@@ -21,86 +21,48 @@ import com.android.ddmlib.log.EventContainer;
 import com.android.ddmlib.log.EventContainer.CompareMethod;
 import com.android.ddmlib.log.EventContainer.EventValueType;
 import com.android.ddmlib.log.EventLogParser;
-import com.android.ddmlib.log.EventValueDescription;
 import com.android.ddmlib.log.EventValueDescription.ValueType;
 import com.android.ddmlib.log.InvalidTypeException;
-import com.android.ddmuilib.DdmUiPreferences;
-import com.android.ddmuilib.TableHelper;
-
-import org.eclipse.jface.preference.IPreferenceStore;
 import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.ControlAdapter;
-import org.eclipse.swt.events.ControlEvent;
 import org.eclipse.swt.events.DisposeEvent;
 import org.eclipse.swt.events.DisposeListener;
 import org.eclipse.swt.graphics.Font;
 import org.eclipse.swt.graphics.FontData;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.ScrollBar;
 import org.eclipse.swt.widgets.Table;
 import org.eclipse.swt.widgets.TableColumn;
-import org.eclipse.swt.widgets.TableItem;
 import org.jfree.chart.ChartFactory;
 import org.jfree.chart.JFreeChart;
-import org.jfree.chart.axis.AxisLocation;
-import org.jfree.chart.axis.NumberAxis;
 import org.jfree.chart.event.ChartChangeEvent;
 import org.jfree.chart.event.ChartChangeEventType;
 import org.jfree.chart.event.ChartChangeListener;
-import org.jfree.chart.labels.CustomXYToolTipGenerator;
 import org.jfree.chart.plot.XYPlot;
-import org.jfree.chart.renderer.xy.AbstractXYItemRenderer;
-import org.jfree.chart.renderer.xy.XYAreaRenderer;
-import org.jfree.chart.renderer.xy.XYBarRenderer;
-import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
 import org.jfree.chart.title.TextTitle;
-import org.jfree.data.time.FixedMillisecond;
 import org.jfree.data.time.Millisecond;
-import org.jfree.data.time.RegularTimePeriod;
-import org.jfree.data.time.SimpleTimePeriod;
-import org.jfree.data.time.TimePeriodValues;
-import org.jfree.data.time.TimePeriodValuesCollection;
 import org.jfree.data.time.TimeSeries;
 import org.jfree.data.time.TimeSeriesCollection;
 import org.jfree.experimental.chart.swt.ChartComposite;
 import org.jfree.experimental.swt.SWTUtils;
-import org.jfree.util.ShapeUtilities;
 
 import java.awt.Color;
 import java.security.InvalidParameterException;
 import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collection;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Scanner;
 import java.util.Set;
-import java.util.TimeZone;
 import java.util.regex.Pattern;
 
 /**
  * Represents a custom display of one or more events.
  */
-final class EventDisplay {
-    
+abstract class EventDisplay {
+
     private final static String DISPLAY_DATA_STORAGE_SEPARATOR = ":"; //$NON-NLS-1$
     private final static String PID_STORAGE_SEPARATOR = ","; //$NON-NLS-1$
     private final static String DESCRIPTOR_STORAGE_SEPARATOR = "$"; //$NON-NLS-1$
     private final static String DESCRIPTOR_DATA_STORAGE_SEPARATOR = "!"; //$NON-NLS-1$
-    
-    private final static String PREFS_COL_DATE = "EventLogPanel.log.Col1"; //$NON-NLS-1$
-    private final static String PREFS_COL_PID = "EventLogPanel.log.Col2"; //$NON-NLS-1$
-    private final static String PREFS_COL_EVENTTAG = "EventLogPanel.log.Col3"; //$NON-NLS-1$
-    private final static String PREFS_COL_VALUENAME = "EventLogPanel.log.Col4"; //$NON-NLS-1$
-    private final static String PREFS_COL_VALUE = "EventLogPanel.log.Col5"; //$NON-NLS-1$
-    private final static String PREFS_COL_TYPE = "EventLogPanel.log.Col6"; //$NON-NLS-1$
 
     private final static String FILTER_VALUE_NULL = "<null>"; //$NON-NLS-1$
 
@@ -111,18 +73,87 @@ final class EventDisplay {
     public final static int DISPLAY_TYPE_SYNC_HIST = 4;
 
     private final static int EVENT_CHECK_FAILED = 0;
-    private final static int EVENT_CHECK_SAME_TAG = 1;
-    private final static int EVENT_CHECK_SAME_VALUE = 2;
-    
+    protected final static int EVENT_CHECK_SAME_TAG = 1;
+    protected final static int EVENT_CHECK_SAME_VALUE = 2;
+
+    // Some common variables for sync display.  These define the sync backends
+    //and how they should be displayed.
+    protected static final int CALENDAR = 0;
+    protected static final int GMAIL = 1;
+    protected static final int FEEDS = 2;
+    protected static final int CONTACTS = 3;
+    protected static final int ERRORS = 4;
+    protected static final int NUM_AUTHS = (CONTACTS + 1);
+    protected static final String AUTH_NAMES[] = {"Calendar", "Gmail", "Feeds", "Contacts", "Errors"};
+    protected static final Color AUTH_COLORS[] = {Color.MAGENTA, Color.GREEN, Color.BLUE, Color.ORANGE, Color.RED};
+
+    // Values from data/etc/event-log-tags
+    final int EVENT_SYNC = 2720;
+    final int EVENT_TICKLE = 2742;
+    final int EVENT_SYNC_DETAILS = 2743;
+
+    /**
+     * Creates the appropriate EventDisplay subclass.
+     *
+     * @param type the type of display (DISPLAY_TYPE_LOG_ALL, etc)
+     * @param name the name of the display
+     * @return the created object
+     */
+    public static EventDisplay eventDisplayFactory(int type, String name) {
+        switch (type) {
+            case DISPLAY_TYPE_LOG_ALL:
+                return new DisplayLog(name);
+            case DISPLAY_TYPE_FILTERED_LOG:
+                return new DisplayFilteredLog(name);
+            case DISPLAY_TYPE_SYNC:
+                return new DisplaySync(name);
+            case DISPLAY_TYPE_SYNC_HIST:
+                return new DisplaySyncHistogram(name);
+            case DISPLAY_TYPE_GRAPH:
+                return new DisplayGraph(name);
+            default:
+                throw new InvalidParameterException("Unknown Display Type"); //$NON-NLS-1$
+        }
+    }
+
+    /**
+     * Adds event to the display.
+     * @param event The event
+     * @param logParser The log parser.
+     */
+    abstract void newEvent(EventContainer event, EventLogParser logParser);
+
+    /**
+     * Resets the display.
+     */
+    abstract void resetUI();
+
+    /**
+     * Gets display type
+     *
+     * @return display type as an integer
+     */
+    abstract int getDisplayType();
+
+    /**
+     * Creates the UI for the event display.
+     *
+     * @param parent    the parent composite.
+     * @param logParser the current log parser.
+     * @return the created control (which may have children).
+     */
+    abstract Control createComposite(final Composite parent, EventLogParser logParser,
+            final ILogColumnListener listener);
+
     interface ILogColumnListener {
         void columnResized(int index, TableColumn sourceColumn);
     }
 
     /**
-     * Describes an event to be displayed. 
+     * Describes an event to be displayed.
      */
     static class OccurrenceDisplayDescriptor {
-        
+
         int eventTag = -1;
         int seriesValueIndex = -1;
         boolean includePid = false;
@@ -158,17 +189,19 @@ final class EventDisplay {
         /**
          * Loads the descriptor parameter from a storage string. The storage string must have
          * been generated with {@link #getStorageString()}.
+         *
          * @param storageString the storage string
          */
         final void loadFrom(String storageString) {
             String[] values = storageString.split(Pattern.quote(DESCRIPTOR_DATA_STORAGE_SEPARATOR));
             loadFrom(values, 0);
         }
-        
+
         /**
          * Loads the parameters from an array of strings.
+         *
          * @param storageStrings the strings representing each parameter.
-         * @param index the starting index in the array of strings.
+         * @param index          the starting index in the array of strings.
          * @return the new index in the array.
          */
         protected int loadFrom(String[] storageStrings, int index) {
@@ -221,7 +254,7 @@ final class EventDisplay {
     }
 
     /**
-     * Describes an event value to be displayed. 
+     * Describes an event value to be displayed.
      */
     static final class ValueDisplayDescriptor extends OccurrenceDisplayDescriptor {
         String valueName;
@@ -253,7 +286,7 @@ final class EventDisplay {
         void replaceWith(OccurrenceDisplayDescriptor descriptor) {
             super.replaceWith(descriptor);
             if (descriptor instanceof ValueDisplayDescriptor) {
-                ValueDisplayDescriptor valueDescriptor = (ValueDisplayDescriptor)descriptor;
+                ValueDisplayDescriptor valueDescriptor = (ValueDisplayDescriptor) descriptor;
                 valueName = valueDescriptor.valueName;
                 valueIndex = valueDescriptor.valueIndex;
             }
@@ -261,8 +294,9 @@ final class EventDisplay {
 
         /**
          * Loads the parameters from an array of strings.
+         *
          * @param storageStrings the strings representing each parameter.
-         * @param index the starting index in the array of strings.
+         * @param index          the starting index in the array of strings.
          * @return the new index in the array.
          */
         @Override
@@ -294,100 +328,78 @@ final class EventDisplay {
     /* ==================
      * Event Display parameters.
      * ================== */
-    private String mName;
-    
-    private int mDisplayType = DISPLAY_TYPE_GRAPH;
+    protected String mName;
+
     private boolean mPidFiltering = false;
 
     private ArrayList<Integer> mPidFilterList = null;
-    
-    private final ArrayList<ValueDisplayDescriptor> mValueDescriptors =
-        new ArrayList<ValueDisplayDescriptor>(); 
+
+    protected final ArrayList<ValueDisplayDescriptor> mValueDescriptors =
+            new ArrayList<ValueDisplayDescriptor>();
     private final ArrayList<OccurrenceDisplayDescriptor> mOccurrenceDescriptors =
-        new ArrayList<OccurrenceDisplayDescriptor>();
+            new ArrayList<OccurrenceDisplayDescriptor>();
 
     /* ==================
      * Event Display members for display purpose.
      * ================== */
     // chart objects
-    /** This is a map of (descriptor, map2) where map2 is a map of (pid, chart-series) */
-    private final HashMap<ValueDisplayDescriptor, HashMap<Integer, TimeSeries>> mValueDescriptorSeriesMap =
-        new HashMap<ValueDisplayDescriptor, HashMap<Integer, TimeSeries>>();
-    /** This is a map of (descriptor, map2) where map2 is a map of (pid, chart-series) */
-    private final HashMap<OccurrenceDisplayDescriptor, HashMap<Integer, TimeSeries>> mOcurrenceDescriptorSeriesMap =
-        new HashMap<OccurrenceDisplayDescriptor, HashMap<Integer, TimeSeries>>();
-
-    /** This is a map of (ValueType, dataset) */
-    private final HashMap<ValueType, TimeSeriesCollection> mValueTypeDataSetMap =
-        new HashMap<ValueType, TimeSeriesCollection>();
-
-    private JFreeChart mChart;
-    private TimeSeriesCollection mOccurrenceDataSet;
-    private int mDataSetCount;
+    /**
+     * This is a map of (descriptor, map2) where map2 is a map of (pid, chart-series)
+     */
+    protected final HashMap<ValueDisplayDescriptor, HashMap<Integer, TimeSeries>> mValueDescriptorSeriesMap =
+            new HashMap<ValueDisplayDescriptor, HashMap<Integer, TimeSeries>>();
+    /**
+     * This is a map of (descriptor, map2) where map2 is a map of (pid, chart-series)
+     */
+    protected final HashMap<OccurrenceDisplayDescriptor, HashMap<Integer, TimeSeries>> mOcurrenceDescriptorSeriesMap =
+            new HashMap<OccurrenceDisplayDescriptor, HashMap<Integer, TimeSeries>>();
+
+    /**
+     * This is a map of (ValueType, dataset)
+     */
+    protected final HashMap<ValueType, TimeSeriesCollection> mValueTypeDataSetMap =
+            new HashMap<ValueType, TimeSeriesCollection>();
+
+    protected JFreeChart mChart;
+    protected TimeSeriesCollection mOccurrenceDataSet;
+    protected int mDataSetCount;
     private ChartComposite mChartComposite;
-    private long mMaximumChartItemAge = -1;
-    private long mHistWidth = 1;
+    protected long mMaximumChartItemAge = -1;
+    protected long mHistWidth = 1;
 
     // log objects.
-    private Table mLogTable;
+    protected Table mLogTable;
 
     /* ==================
      * Misc data.
      * ================== */
-    private int mValueDescriptorCheck = EVENT_CHECK_FAILED;
-
-    /**
-     * Loads a new {@link EventDisplay} from a storage string. The string must have been created
-     * with {@link #getStorageString()}.
-     * @param storageString the storage string
-     * @return a new {@link EventDisplay} or null if the load failed.
-     */
-    static EventDisplay load(String storageString) {
-        EventDisplay ed = new EventDisplay();
-        if (ed.loadFrom(storageString)) {
-            return ed;
-        }
-
-        return null;
-    }
+    protected int mValueDescriptorCheck = EVENT_CHECK_FAILED;
 
     EventDisplay(String name) {
         mName = name;
     }
 
-    /**
-     * Builds an {@link EventDisplay}.
-     * @param name the name of the display
-     * @param displayType the display type: {@link #DISPLAY_TYPE_GRAPH} or
-     * {@value #DISPLAY_TYPE_FILTERED_LOG}.
-     * @param filterByPid the flag indicating whether to filter by pid.
-     */
-    EventDisplay(String name, int displayType, boolean filterByPid) {
-        mName = name;
-        mDisplayType = displayType;
-        mPidFiltering = filterByPid;
-    }
-
-    EventDisplay(EventDisplay from) {
-        mName = from.mName;
-        mDisplayType = from.mDisplayType;
-        mPidFiltering = from.mPidFiltering;
-        mMaximumChartItemAge = from.mMaximumChartItemAge;
-        mHistWidth = from.mHistWidth;
+    static EventDisplay clone(EventDisplay from) {
+        EventDisplay ed = eventDisplayFactory(from.getDisplayType(), from.getName());
+        ed.mName = from.mName;
+        ed.mPidFiltering = from.mPidFiltering;
+        ed.mMaximumChartItemAge = from.mMaximumChartItemAge;
+        ed.mHistWidth = from.mHistWidth;
 
         if (from.mPidFilterList != null) {
-            mPidFilterList = new ArrayList<Integer>();
-            mPidFilterList.addAll(from.mPidFilterList);
+            ed.mPidFilterList = new ArrayList<Integer>();
+            ed.mPidFilterList.addAll(from.mPidFilterList);
         }
 
         for (ValueDisplayDescriptor desc : from.mValueDescriptors) {
-            mValueDescriptors.add(new ValueDisplayDescriptor(desc));
+            ed.mValueDescriptors.add(new ValueDisplayDescriptor(desc));
         }
-        mValueDescriptorCheck = from.mValueDescriptorCheck;
+        ed.mValueDescriptorCheck = from.mValueDescriptorCheck;
 
         for (OccurrenceDisplayDescriptor desc : from.mOccurrenceDescriptors) {
-            mOccurrenceDescriptors.add(new OccurrenceDisplayDescriptor(desc));
+            ed.mOccurrenceDescriptors.add(new OccurrenceDisplayDescriptor(desc));
         }
+        return ed;
     }
 
     /**
@@ -398,7 +410,7 @@ final class EventDisplay {
 
         sb.append(mName);
         sb.append(DISPLAY_DATA_STORAGE_SEPARATOR);
-        sb.append(mDisplayType);
+        sb.append(getDisplayType());
         sb.append(DISPLAY_DATA_STORAGE_SEPARATOR);
         sb.append(Boolean.toString(mPidFiltering));
         sb.append(DISPLAY_DATA_STORAGE_SEPARATOR);
@@ -419,35 +431,27 @@ final class EventDisplay {
     void setName(String name) {
         mName = name;
     }
-    
+
     String getName() {
         return mName;
     }
-    
-    void setDisplayType(int value) {
-        mDisplayType = value;
-    }
-    
-    int getDisplayType() {
-        return mDisplayType;
-    }
-    
+
     void setPidFiltering(boolean filterByPid) {
         mPidFiltering = filterByPid;
     }
-    
+
     boolean getPidFiltering() {
         return mPidFiltering;
     }
-    
+
     void setPidFilterList(ArrayList<Integer> pids) {
         if (mPidFiltering == false) {
             new InvalidParameterException();
         }
-        
+
         mPidFilterList = pids;
     }
-    
+
     ArrayList<Integer> getPidFilterList() {
         return mPidFilterList;
     }
@@ -456,11 +460,11 @@ final class EventDisplay {
         if (mPidFiltering == false) {
             new InvalidParameterException();
         }
-        
+
         if (mPidFilterList == null) {
             mPidFilterList = new ArrayList<Integer>();
         }
-        
+
         mPidFilterList.add(pid);
     }
 
@@ -485,27 +489,29 @@ final class EventDisplay {
     Iterator<OccurrenceDisplayDescriptor> getOccurrenceDescriptors() {
         return mOccurrenceDescriptors.iterator();
     }
-    
+
     /**
-     * Adds a descriptor. This can be a {@link OccurrenceDisplayDescriptor} or a 
+     * Adds a descriptor. This can be a {@link OccurrenceDisplayDescriptor} or a
      * {@link ValueDisplayDescriptor}.
+     *
      * @param descriptor the descriptor to be added.
      */
     void addDescriptor(OccurrenceDisplayDescriptor descriptor) {
         if (descriptor instanceof ValueDisplayDescriptor) {
-            mValueDescriptors.add((ValueDisplayDescriptor)descriptor);
+            mValueDescriptors.add((ValueDisplayDescriptor) descriptor);
             mValueDescriptorCheck = checkDescriptors();
         } else {
             mOccurrenceDescriptors.add(descriptor);
         }
     }
-    
+
     /**
      * Returns a descriptor by index and class (extending {@link OccurrenceDisplayDescriptor}).
+     *
      * @param descriptorClass the class of the descriptor to return.
-     * @param index the index of the descriptor to return.
+     * @param index           the index of the descriptor to return.
      * @return either a {@link OccurrenceDisplayDescriptor} or a {@link ValueDisplayDescriptor}
-     * or <code>null</code> if <code>descriptorClass</code> is another class.
+     *         or <code>null</code> if <code>descriptorClass</code> is another class.
      */
     OccurrenceDisplayDescriptor getDescriptor(
             Class<? extends OccurrenceDisplayDescriptor> descriptorClass, int index) {
@@ -515,14 +521,15 @@ final class EventDisplay {
         } else if (descriptorClass == ValueDisplayDescriptor.class) {
             return mValueDescriptors.get(index);
         }
-        
+
         return null;
     }
-    
+
     /**
      * Removes a descriptor based on its class and index.
+     *
      * @param descriptorClass the class of the descriptor.
-     * @param index the index of the descriptor to be removed.
+     * @param index           the index of the descriptor to be removed.
      */
     void removeDescriptor(Class<? extends OccurrenceDisplayDescriptor> descriptorClass, int index) {
         if (descriptorClass == OccurrenceDisplayDescriptor.class) {
@@ -533,137 +540,107 @@ final class EventDisplay {
         }
     }
 
-    /**
-     * Creates the UI for the event display.
-     * @param parent the parent composite.
-     * @param logParser the current log parser.
-     * @return the created control (which may have children).
-     */
-    Control createComposite(final Composite parent, EventLogParser logParser,
-            final ILogColumnListener listener) {
-        switch (mDisplayType) {
-            case DISPLAY_TYPE_LOG_ALL:
-                // intended fall-through
-            case DISPLAY_TYPE_FILTERED_LOG:
-                return createLogUI(parent, listener);
-            case DISPLAY_TYPE_GRAPH:
-                // intended fall-through
-            case DISPLAY_TYPE_SYNC:
-                // intended fall-through
-            case DISPLAY_TYPE_SYNC_HIST:
-                String title = getChartTitle(logParser);
-                mChart = ChartFactory.createTimeSeriesChart(
-                        null,
-                        null /* timeAxisLabel */,
-                        null /* valueAxisLabel */,
-                        null, /* dataset. set below */
-                        true /* legend */,
-                        false /* tooltips */,
-                        false /* urls */);
-                
-                // get the font to make a proper title. We need to convert the swt font,
-                // into an awt font.
-                Font f = parent.getFont();
-                FontData[] fData = f.getFontData();
-                
-                // event though on Mac OS there could be more than one fontData, we'll only use
-                // the first one.
-                FontData firstFontData = fData[0];
-
-                java.awt.Font awtFont = SWTUtils.toAwtFont(parent.getDisplay(),
-                        firstFontData, true /* ensureSameSize */);
-
-                if (mDisplayType == DISPLAY_TYPE_SYNC) {
-                    title = "Sync Status";
-                } else if (mDisplayType == DISPLAY_TYPE_SYNC_HIST) {
-                    title = "Sync Histogram";
+    Control createCompositeChart(final Composite parent, EventLogParser logParser,
+            String title) {
+        mChart = ChartFactory.createTimeSeriesChart(
+                null,
+                null /* timeAxisLabel */,
+                null /* valueAxisLabel */,
+                null, /* dataset. set below */
+                true /* legend */,
+                false /* tooltips */,
+                false /* urls */);
+
+        // get the font to make a proper title. We need to convert the swt font,
+        // into an awt font.
+        Font f = parent.getFont();
+        FontData[] fData = f.getFontData();
+
+        // event though on Mac OS there could be more than one fontData, we'll only use
+        // the first one.
+        FontData firstFontData = fData[0];
+
+        java.awt.Font awtFont = SWTUtils.toAwtFont(parent.getDisplay(),
+                firstFontData, true /* ensureSameSize */);
+
+
+        mChart.setTitle(new TextTitle(title, awtFont));
+
+        final XYPlot xyPlot = mChart.getXYPlot();
+        xyPlot.setRangeCrosshairVisible(true);
+        xyPlot.setRangeCrosshairLockedOnData(true);
+        xyPlot.setDomainCrosshairVisible(true);
+        xyPlot.setDomainCrosshairLockedOnData(true);
+
+        mChart.addChangeListener(new ChartChangeListener() {
+            public void chartChanged(ChartChangeEvent event) {
+                ChartChangeEventType type = event.getType();
+                if (type == ChartChangeEventType.GENERAL) {
+                    // because the value we need (rangeCrosshair and domainCrosshair) are
+                    // updated on the draw, but the notification happens before the draw,
+                    // we process the click in a future runnable!
+                    parent.getDisplay().asyncExec(new Runnable() {
+                        public void run() {
+                            processClick(xyPlot);
+                        }
+                    });
                 }
+            }
+        });
 
-                mChart.setTitle(new TextTitle(title, awtFont));
-                
-                final XYPlot xyPlot = mChart.getXYPlot();
-                xyPlot.setRangeCrosshairVisible(true);
-                xyPlot.setRangeCrosshairLockedOnData(true);
-                xyPlot.setDomainCrosshairVisible(true);
-                xyPlot.setDomainCrosshairLockedOnData(true);
-
-                mChart.addChangeListener(new ChartChangeListener() {
-                    public void chartChanged(ChartChangeEvent event) {
-                        ChartChangeEventType type = event.getType();
-                        if (type == ChartChangeEventType.GENERAL) {
-                            // because the value we need (rangeCrosshair and domainCrosshair) are
-                            // updated on the draw, but the notification happens before the draw,
-                            // we process the click in a future runnable!
-                            parent.getDisplay().asyncExec(new Runnable() {
-                                public void run() {
-                                    processClick(xyPlot);
-                                }
-                            });
-                        }
-                    }
-                });
-
-                mChartComposite = new ChartComposite(parent, SWT.BORDER, mChart,
-                        ChartComposite.DEFAULT_WIDTH,
-                        ChartComposite.DEFAULT_HEIGHT,
-                        ChartComposite.DEFAULT_MINIMUM_DRAW_WIDTH,
-                        ChartComposite.DEFAULT_MINIMUM_DRAW_HEIGHT,
-                        3000, // max draw width. We don't want it to zoom, so we put a big number
-                        3000, // max draw height. We don't want it to zoom, so we put a big number
-                        true,  // off-screen buffer
-                        true,  // properties
-                        true,  // save
-                        true,  // print
-                        true,  // zoom
-                        true);   // tooltips
-
-                mChartComposite.addDisposeListener(new DisposeListener() {
-                    public void widgetDisposed(DisposeEvent e) {
-                        mValueTypeDataSetMap.clear();
-                        mDataSetCount = 0;
-                        mOccurrenceDataSet = null;
-                        mChart = null;
-                        mChartComposite = null;
-                        mValueDescriptorSeriesMap.clear();
-                        mOcurrenceDescriptorSeriesMap.clear();
-                    }
-                });
+        mChartComposite = new ChartComposite(parent, SWT.BORDER, mChart,
+                ChartComposite.DEFAULT_WIDTH,
+                ChartComposite.DEFAULT_HEIGHT,
+                ChartComposite.DEFAULT_MINIMUM_DRAW_WIDTH,
+                ChartComposite.DEFAULT_MINIMUM_DRAW_HEIGHT,
+                3000, // max draw width. We don't want it to zoom, so we put a big number
+                3000, // max draw height. We don't want it to zoom, so we put a big number
+                true,  // off-screen buffer
+                true,  // properties
+                true,  // save
+                true,  // print
+                true,  // zoom
+                true);   // tooltips
+
+        mChartComposite.addDisposeListener(new DisposeListener() {
+            public void widgetDisposed(DisposeEvent e) {
+                mValueTypeDataSetMap.clear();
+                mDataSetCount = 0;
+                mOccurrenceDataSet = null;
+                mChart = null;
+                mChartComposite = null;
+                mValueDescriptorSeriesMap.clear();
+                mOcurrenceDescriptorSeriesMap.clear();
+            }
+        });
 
-                if (mDisplayType == DISPLAY_TYPE_SYNC) {
-                    initSyncDisplay();
-                } else if (mDisplayType == DISPLAY_TYPE_SYNC_HIST) {
-                    initSyncHistDisplay();
-                }
+        return mChartComposite;
 
-                return mChartComposite;
-            default:
-                    throw new InvalidParameterException("Unknown Display Type"); //$NON-NLS-1$
-        }
     }
 
     private void processClick(XYPlot xyPlot) {
         double rangeValue = xyPlot.getRangeCrosshairValue();
-        if (rangeValue != 0) { 
+        if (rangeValue != 0) {
             double domainValue = xyPlot.getDomainCrosshairValue();
-            
-            Millisecond msec = new Millisecond(new Date((long)domainValue));
-            
+
+            Millisecond msec = new Millisecond(new Date((long) domainValue));
+
             // look for values in the dataset that contains data at this TimePeriod
             Set<ValueDisplayDescriptor> descKeys = mValueDescriptorSeriesMap.keySet();
-         
+
             for (ValueDisplayDescriptor descKey : descKeys) {
                 HashMap<Integer, TimeSeries> map = mValueDescriptorSeriesMap.get(descKey);
-                
+
                 Set<Integer> pidKeys = map.keySet();
-                
+
                 for (Integer pidKey : pidKeys) {
                     TimeSeries series = map.get(pidKey);
-                    
+
                     Number value = series.getValue(msec);
                     if (value != null) {
                         // found a match. lets check against the actual value.
                         if (value.doubleValue() == rangeValue) {
-                            
+
                             return;
                         }
                     }
@@ -672,176 +649,29 @@ final class EventDisplay {
         }
     }
 
-    /**
-     * Creates the UI for a log display.
-     * @param parent the parent {@link Composite}
-     * @param listener the {@link ILogColumnListener} to notify on column resize events.
-     * @return the top Composite of the UI.
-     */
-    private Control createLogUI(Composite parent, final ILogColumnListener listener) {
-        Composite mainComp = new Composite(parent, SWT.NONE);
-        GridLayout gl;
-        mainComp.setLayout(gl = new GridLayout(1, false));
-        gl.marginHeight = gl.marginWidth = 0;
-        mainComp.addDisposeListener(new DisposeListener() {
-            public void widgetDisposed(DisposeEvent e) {
-                mLogTable = null;
-            }
-        });
-
-        Label l = new Label(mainComp, SWT.CENTER);
-        l.setText(mName);
-        l.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
-
-        mLogTable = new Table(mainComp, SWT.MULTI | SWT.FULL_SELECTION | SWT.V_SCROLL |
-                SWT.BORDER);
-        mLogTable.setLayoutData(new GridData(GridData.FILL_BOTH));
-        
-        IPreferenceStore store = DdmUiPreferences.getStore();
-
-        TableColumn col = TableHelper.createTableColumn(
-                mLogTable, "Time",
-                SWT.LEFT, "0000-00-00 00:00:00", PREFS_COL_DATE, store); //$NON-NLS-1$
-        col.addControlListener(new ControlAdapter() {
-            @Override
-            public void controlResized(ControlEvent e) {
-                Object source = e.getSource();
-                if (source instanceof TableColumn) {
-                    listener.columnResized(0, (TableColumn)source);
-                }
-            }
-        });
-
-        col = TableHelper.createTableColumn(
-                mLogTable, "pid",
-                SWT.LEFT, "0000", PREFS_COL_PID, store); //$NON-NLS-1$
-        col.addControlListener(new ControlAdapter() {
-            @Override
-            public void controlResized(ControlEvent e) {
-                Object source = e.getSource();
-                if (source instanceof TableColumn) {
-                    listener.columnResized(1, (TableColumn)source);
-                }
-            }
-        });
-
-        col = TableHelper.createTableColumn(
-                mLogTable, "Event",
-                SWT.LEFT, "abcdejghijklmno", PREFS_COL_EVENTTAG, store); //$NON-NLS-1$
-        col.addControlListener(new ControlAdapter() {
-            @Override
-            public void controlResized(ControlEvent e) {
-                Object source = e.getSource();
-                if (source instanceof TableColumn) {
-                    listener.columnResized(2, (TableColumn)source);
-                }
-            }
-        });
-
-        col = TableHelper.createTableColumn(
-                mLogTable, "Name",
-                SWT.LEFT, "Process Name", PREFS_COL_VALUENAME, store); //$NON-NLS-1$
-        col.addControlListener(new ControlAdapter() {
-            @Override
-            public void controlResized(ControlEvent e) {
-                Object source = e.getSource();
-                if (source instanceof TableColumn) {
-                    listener.columnResized(3, (TableColumn)source);
-                }
-            }
-        });
-
-        col = TableHelper.createTableColumn(
-                mLogTable, "Value",
-                SWT.LEFT, "0000000", PREFS_COL_VALUE, store); //$NON-NLS-1$
-        col.addControlListener(new ControlAdapter() {
-            @Override
-            public void controlResized(ControlEvent e) {
-                Object source = e.getSource();
-                if (source instanceof TableColumn) {
-                    listener.columnResized(4, (TableColumn)source);
-                }
-            }
-        });
-
-        col = TableHelper.createTableColumn(
-                mLogTable, "Type",
-                SWT.LEFT, "long, seconds", PREFS_COL_TYPE, store); //$NON-NLS-1$
-        col.addControlListener(new ControlAdapter() {
-            @Override
-            public void controlResized(ControlEvent e) {
-                Object source = e.getSource();
-                if (source instanceof TableColumn) {
-                    listener.columnResized(5, (TableColumn)source);
-                }
-            }
-        });
-        
-        mLogTable.setHeaderVisible(true);
-        mLogTable.setLinesVisible(true);
 
-        return mainComp;
-    }
-    
     /**
      * Resizes the <code>index</code>-th column of the log {@link Table} (if applicable).
+     * Subclasses can override if necessary.
      * <p/>
      * This does nothing if the <code>Table</code> object is <code>null</code> (because the display
      * type does not use a column) or if the <code>index</code>-th column is in fact the originating
      * column passed as argument.
-     * @param index the index of the column to resize
+     *
+     * @param index        the index of the column to resize
      * @param sourceColumn the original column that was resize, and on which we need to sync the
-     * index-th column width.
+     *                     index-th column width.
      */
     void resizeColumn(int index, TableColumn sourceColumn) {
-        if (mLogTable != null) {
-            TableColumn col = mLogTable.getColumn(index);
-            if (col != sourceColumn) {
-                col.setWidth(sourceColumn.getWidth());
-            }
-        }
     }
-    
+
     /**
      * Sets the current {@link EventLogParser} object.
+     * Subclasses can override if necessary.
      */
-    void setNewLogParser(EventLogParser logParser) {
-        if (mDisplayType == DISPLAY_TYPE_GRAPH) {
-            if (mChart != null) {
-                mChart.setTitle(getChartTitle(logParser));
-            }
-        }
-    }
-    
-    void resetUI() {
-        switch (mDisplayType) {
-            case DISPLAY_TYPE_LOG_ALL:
-                // intended fall-through
-            case DISPLAY_TYPE_FILTERED_LOG:
-                mLogTable.removeAll();
-                break;
-            case DISPLAY_TYPE_SYNC:
-                initSyncDisplay();
-                break;
-            case DISPLAY_TYPE_SYNC_HIST:
-                initSyncHistDisplay();
-                break;
-            case DISPLAY_TYPE_GRAPH:
-                Collection<TimeSeriesCollection> datasets = mValueTypeDataSetMap.values();
-                for (TimeSeriesCollection dataset : datasets) {
-                    dataset.removeAllSeries();
-                }
-                if (mOccurrenceDataSet != null) {
-                    mOccurrenceDataSet.removeAllSeries();
-                }
-                mValueDescriptorSeriesMap.clear();
-                mOcurrenceDescriptorSeriesMap.clear();
-                break;
-            default:
-                throw new InvalidParameterException("Unknown Display Type"); //$NON-NLS-1$
-        }
+    protected void setNewLogParser(EventLogParser logParser) {
     }
-    
+
     /**
      * Prepares the {@link EventDisplay} for a multi event display.
      */
@@ -861,102 +691,70 @@ final class EventDisplay {
     }
 
     /**
-     * Processes a new event. This must be called from the ui thread.
-     * @param event the event to process.
-     */
-    void newEvent(EventContainer event, EventLogParser logParser) {
-        ArrayList<ValueDisplayDescriptor> valueDescriptors =
-            new ArrayList<ValueDisplayDescriptor>();
-
-        ArrayList<OccurrenceDisplayDescriptor> occurrenceDescriptors =
-            new ArrayList<OccurrenceDisplayDescriptor>();
-
-        if (filterEvent(event, valueDescriptors, occurrenceDescriptors)) {
-            switch (mDisplayType) {
-                case DISPLAY_TYPE_LOG_ALL:
-                    addToLog(event, logParser);
-                    break;
-                case DISPLAY_TYPE_FILTERED_LOG:
-                    addToLog(event, logParser, valueDescriptors, occurrenceDescriptors);
-                    break;
-                case DISPLAY_TYPE_SYNC:
-                    updateSyncDisplay(event);
-                    break;
-                case DISPLAY_TYPE_SYNC_HIST:
-                    updateSyncHistDisplay(event);
-                    break;
-                case DISPLAY_TYPE_GRAPH:
-                    updateChart(event, logParser, valueDescriptors, occurrenceDescriptors);
-                    break;
-                default:
-                    throw new InvalidParameterException("Unknown Display Type"); //$NON-NLS-1$
-            }
-        }
-    }
-    
-    /**
      * Returns the {@link Table} object used to display events, if any.
+     *
      * @return a Table object or <code>null</code>.
      */
     Table getTable() {
         return mLogTable;
     }
 
-    /** Private constructor used for loading from storage */
-    private EventDisplay() {
-        // nothing to be done here.
-    }
-    
     /**
-     * Loads the {@link EventDisplay} parameters from the storage string.
-     * @param storageString the string containing the parameters.
+     * Loads a new {@link EventDisplay} from a storage string. The string must have been created
+     * with {@link #getStorageString()}.
+     *
+     * @param storageString the storage string
+     * @return a new {@link EventDisplay} or null if the load failed.
      */
-    private boolean loadFrom(String storageString) {
+    static EventDisplay load(String storageString) {
         if (storageString.length() > 0) {
             // the storage string is separated by ':'
             String[] values = storageString.split(Pattern.quote(DISPLAY_DATA_STORAGE_SEPARATOR));
-    
+
             try {
                 int index = 0;
-    
-                mName = values[index++];
-                mDisplayType = Integer.parseInt(values[index++]);
-                mPidFiltering = Boolean.parseBoolean(values[index++]);
+
+                String name = values[index++];
+                int displayType = Integer.parseInt(values[index++]);
+                boolean pidFiltering = Boolean.parseBoolean(values[index++]);
+
+                EventDisplay ed = eventDisplayFactory(displayType, name);
+                ed.setPidFiltering(pidFiltering);
 
                 // because empty sections are removed by String.split(), we have to check
                 // the index for those.
                 if (index < values.length) {
-                    loadPidFilters(values[index++]);
+                    ed.loadPidFilters(values[index++]);
                 }
-    
+
                 if (index < values.length) {
-                    loadValueDescriptors(values[index++]);
+                    ed.loadValueDescriptors(values[index++]);
                 }
-    
+
                 if (index < values.length) {
-                    loadOccurrenceDescriptors(values[index++]);
+                    ed.loadOccurrenceDescriptors(values[index++]);
                 }
-                
-                updateValueDescriptorCheck();
-                
+
+                ed.updateValueDescriptorCheck();
+
                 if (index < values.length) {
-                    mMaximumChartItemAge = Long.parseLong(values[index++]);
+                    ed.mMaximumChartItemAge = Long.parseLong(values[index++]);
                 }
 
                 if (index < values.length) {
-                    mHistWidth = Long.parseLong(values[index++]);
+                    ed.mHistWidth = Long.parseLong(values[index++]);
                 }
-    
-                return true;
+
+                return ed;
             } catch (RuntimeException re) {
-                // we'll return false below.
+                // we'll return null below.
                 Log.e("ddms", re);
             }
         }
-        
-        return false;
+
+        return null;
     }
-    
+
     private String getPidStorageString() {
         if (mPidFilterList != null) {
             StringBuilder sb = new StringBuilder();
@@ -969,17 +767,17 @@ final class EventDisplay {
                 }
                 sb.append(i);
             }
-            
+
             return sb.toString();
         }
         return ""; //$NON-NLS-1$
     }
 
-    
+
     private void loadPidFilters(String storageString) {
         if (storageString.length() > 0) {
             String[] values = storageString.split(Pattern.quote(PID_STORAGE_SEPARATOR));
-            
+
             for (String value : values) {
                 if (mPidFilterList == null) {
                     mPidFilterList = new ArrayList<Integer>();
@@ -988,12 +786,12 @@ final class EventDisplay {
             }
         }
     }
-    
+
     private String getDescriptorStorageString(
             ArrayList<? extends OccurrenceDisplayDescriptor> descriptorList) {
         StringBuilder sb = new StringBuilder();
         boolean first = true;
-        
+
         for (OccurrenceDisplayDescriptor descriptor : descriptorList) {
             if (first == false) {
                 sb.append(DESCRIPTOR_STORAGE_SEPARATOR);
@@ -1002,7 +800,7 @@ final class EventDisplay {
             }
             sb.append(descriptor.getStorageString());
         }
-        
+
         return sb.toString();
     }
 
@@ -1012,21 +810,21 @@ final class EventDisplay {
         }
 
         String[] values = storageString.split(Pattern.quote(DESCRIPTOR_STORAGE_SEPARATOR));
-        
+
         for (String value : values) {
             OccurrenceDisplayDescriptor desc = new OccurrenceDisplayDescriptor();
             desc.loadFrom(value);
             mOccurrenceDescriptors.add(desc);
         }
     }
-    
+
     private void loadValueDescriptors(String storageString) {
         if (storageString.length() == 0) {
             return;
         }
 
         String[] values = storageString.split(Pattern.quote(DESCRIPTOR_STORAGE_SEPARATOR));
-        
+
         for (String value : values) {
             ValueDisplayDescriptor desc = new ValueDisplayDescriptor();
             desc.loadFrom(value);
@@ -1035,292 +833,12 @@ final class EventDisplay {
     }
 
     /**
-     * Returns the {@link TimeSeriesCollection} for the occurrence display. If the data set is not
-     * yet created, it is first allocated and set up into the {@link JFreeChart} object.
-     */
-    private TimeSeriesCollection getOccurrenceDataSet() {
-        if (mOccurrenceDataSet == null) {
-            mOccurrenceDataSet = new TimeSeriesCollection();
-
-            XYPlot xyPlot = mChart.getXYPlot();
-            xyPlot.setDataset(mDataSetCount, mOccurrenceDataSet);
-
-            OccurrenceRenderer renderer = new OccurrenceRenderer();
-            renderer.setBaseShapesVisible(false);
-            xyPlot.setRenderer(mDataSetCount, renderer);
-            
-            mDataSetCount++;
-        }
-        
-        return mOccurrenceDataSet;
-    }
-    
-    /**
-     * Returns a {@link TimeSeriesCollection} for a specific {@link ValueType}.
-     * If the data set is not yet created, it is first allocated and set up into the
-     * {@link JFreeChart} object.
-     * @param type the {@link ValueType} of the data set.
-     * @param accumulateValues 
-     */
-    private TimeSeriesCollection getValueDataset(ValueType type, boolean accumulateValues) {
-        TimeSeriesCollection dataset = mValueTypeDataSetMap.get(type);
-        if (dataset == null) {
-            // create the data set and store it in the map
-            dataset = new TimeSeriesCollection();
-            mValueTypeDataSetMap.put(type, dataset);
-
-            // create the renderer and configure it depending on the ValueType
-            AbstractXYItemRenderer renderer;
-            if (type == ValueType.PERCENT && accumulateValues) {
-                renderer = new XYAreaRenderer();
-            } else {
-                XYLineAndShapeRenderer r = new XYLineAndShapeRenderer();
-                r.setBaseShapesVisible(type != ValueType.PERCENT);
-                
-                renderer = r;
-            }
-
-            // set both the dataset and the renderer in the plot object.
-            XYPlot xyPlot = mChart.getXYPlot();
-            xyPlot.setDataset(mDataSetCount, dataset);
-            xyPlot.setRenderer(mDataSetCount, renderer);
-            
-            // put a new axis label, and configure it.
-            NumberAxis axis = new NumberAxis(type.toString());
-            
-            if (type == ValueType.PERCENT) {
-                // force percent range to be (0,100) fixed.
-                axis.setAutoRange(false);
-                axis.setRange(0., 100.);
-            }
-
-            // for the index, we ignore the occurrence dataset
-            int count = mDataSetCount;
-            if (mOccurrenceDataSet != null) {
-                count--;
-            }
-
-            xyPlot.setRangeAxis(count, axis);
-            if ((count % 2) == 0) {
-                xyPlot.setRangeAxisLocation(count, AxisLocation.BOTTOM_OR_LEFT);
-            } else {
-                xyPlot.setRangeAxisLocation(count, AxisLocation.TOP_OR_RIGHT);
-            }
-            
-            // now we link the dataset and the axis
-            xyPlot.mapDatasetToRangeAxis(mDataSetCount, count);
-            
-            mDataSetCount++;
-        }
-
-        return dataset;
-    }
-
-
-    /**
-     * Updates the chart with the {@link EventContainer} by adding the values/occurrences defined
-     * by the {@link ValueDisplayDescriptor} and {@link OccurrenceDisplayDescriptor} objects from
-     * the two lists.
-     * <p/>This method is only called when at least one of the descriptor list is non empty.
-     * @param event
-     * @param logParser
-     * @param valueDescriptors
-     * @param occurrenceDescriptors
-     */
-    private void updateChart(EventContainer event, EventLogParser logParser,
-            ArrayList<ValueDisplayDescriptor> valueDescriptors,
-            ArrayList<OccurrenceDisplayDescriptor> occurrenceDescriptors) {
-        Map<Integer, String> tagMap = logParser.getTagMap();
-
-        Millisecond millisecondTime = null;
-        long msec = -1;
-        
-        // If the event container is a cpu container (tag == 2721), and there is no descriptor
-        // for the total CPU load, then we do accumulate all the values.
-        boolean accumulateValues = false;
-        double accumulatedValue = 0;
-        
-        if (event.mTag == 2721) {
-            accumulateValues = true;
-            for (ValueDisplayDescriptor descriptor : valueDescriptors) {
-                accumulateValues &= (descriptor.valueIndex != 0); 
-            }
-        }
-        
-        for (ValueDisplayDescriptor descriptor : valueDescriptors) {
-            try {
-                // get the hashmap for this descriptor
-                HashMap<Integer, TimeSeries> map = mValueDescriptorSeriesMap.get(descriptor);
-
-                // if it's not there yet, we create it.
-                if (map == null) {
-                    map = new HashMap<Integer, TimeSeries>();
-                    mValueDescriptorSeriesMap.put(descriptor, map);
-                }
-                
-                // get the TimeSeries for this pid
-                TimeSeries timeSeries = map.get(event.pid);
-
-                // if it doesn't exist yet, we create it
-                if (timeSeries == null) {
-                    // get the series name
-                    String seriesFullName = null;
-                    String seriesLabel = getSeriesLabel(event, descriptor);
-
-                    switch (mValueDescriptorCheck) {
-                        case EVENT_CHECK_SAME_TAG:
-                            seriesFullName = String.format("%1$s / %2$s", seriesLabel,
-                                    descriptor.valueName);
-                            break;
-                        case EVENT_CHECK_SAME_VALUE:
-                            seriesFullName = String.format("%1$s", seriesLabel);
-                            break;
-                        default:
-                            seriesFullName = String.format("%1$s / %2$s: %3$s", seriesLabel,
-                                    tagMap.get(descriptor.eventTag),
-                                    descriptor.valueName);
-                            break;
-                    }
-
-                    // get the data set for this ValueType
-                    TimeSeriesCollection dataset = getValueDataset(
-                            logParser.getEventInfoMap().get(event.mTag)[descriptor.valueIndex]
-                                                                        .getValueType(),
-                            accumulateValues);
-
-                    // create the series
-                    timeSeries = new TimeSeries(seriesFullName, Millisecond.class);
-                    if (mMaximumChartItemAge != -1) {
-                        timeSeries.setMaximumItemAge(mMaximumChartItemAge * 1000);
-                    }
-                    
-                    dataset.addSeries(timeSeries);
-                    
-                    // add it to the map.
-                    map.put(event.pid, timeSeries);
-                }
-                
-                // update the timeSeries.
-    
-                // get the value from the event
-                double value = event.getValueAsDouble(descriptor.valueIndex);
-                
-                // accumulate the values if needed.
-                if (accumulateValues) {
-                    accumulatedValue += value;
-                    value = accumulatedValue;
-                }
-                
-                // get the time
-                if (millisecondTime == null) {
-                    msec = (long)event.sec * 1000L + (event.nsec / 1000000L);
-                    millisecondTime = new Millisecond(new Date(msec));
-                }
-
-                // add the value to the time series
-                timeSeries.addOrUpdate(millisecondTime, value);
-            } catch (InvalidTypeException e) {
-                // just ignore this descriptor if there's a type mismatch
-            }
-        }
-
-        for (OccurrenceDisplayDescriptor descriptor : occurrenceDescriptors) {
-            try {
-                // get the hashmap for this descriptor
-                HashMap<Integer, TimeSeries> map = mOcurrenceDescriptorSeriesMap.get(descriptor);
-    
-                // if it's not there yet, we create it.
-                if (map == null) {
-                    map = new HashMap<Integer, TimeSeries>();
-                    mOcurrenceDescriptorSeriesMap.put(descriptor, map);
-                }
-
-                // get the TimeSeries for this pid
-                TimeSeries timeSeries = map.get(event.pid);
-
-                // if it doesn't exist yet, we create it.
-                if (timeSeries == null) {
-                    String seriesLabel = getSeriesLabel(event, descriptor);
-    
-                    String seriesFullName = String.format("[%1$s:%2$s]",
-                            tagMap.get(descriptor.eventTag), seriesLabel);
-    
-                    timeSeries = new TimeSeries(seriesFullName, Millisecond.class);
-                    if (mMaximumChartItemAge != -1) {
-                        timeSeries.setMaximumItemAge(mMaximumChartItemAge);
-                    }
-
-                    getOccurrenceDataSet().addSeries(timeSeries);
-    
-                    map.put(event.pid, timeSeries);
-                }
-                
-                // update the series
-                
-                // get the time
-                if (millisecondTime == null) {
-                    msec = (long)event.sec * 1000L + (event.nsec / 1000000L);
-                    millisecondTime = new Millisecond(new Date(msec));
-                }
-    
-                // add the value to the time series
-                timeSeries.addOrUpdate(millisecondTime, 0); // the value is unused
-            } catch (InvalidTypeException e) {
-                // just ignore this descriptor if there's a type mismatch
-            }
-        }
-        
-        // go through all the series and remove old values.
-        if (msec != -1 && mMaximumChartItemAge != -1) {
-            Collection<HashMap<Integer, TimeSeries>> pidMapValues =
-                mValueDescriptorSeriesMap.values();
-
-            for (HashMap<Integer, TimeSeries> pidMapValue : pidMapValues) {
-                Collection<TimeSeries> seriesCollection = pidMapValue.values();
-                
-                for (TimeSeries timeSeries : seriesCollection) {
-                    timeSeries.removeAgedItems(msec, true);
-                }
-            }
-    
-            pidMapValues = mOcurrenceDescriptorSeriesMap.values();
-            for (HashMap<Integer, TimeSeries> pidMapValue : pidMapValues) {
-                Collection<TimeSeries> seriesCollection = pidMapValue.values();
-                
-                for (TimeSeries timeSeries : seriesCollection) {
-                    timeSeries.removeAgedItems(msec, true);
-                }
-            }
-        }
-    }
-
-    /**
-     * Return the series label for this event. This only contains the pid information.
-     * @param event the {@link EventContainer}
-     * @param descriptor the {@link OccurrenceDisplayDescriptor}
-     * @return the series label.
-     * @throws InvalidTypeException
-     */
-    private String getSeriesLabel(EventContainer event, OccurrenceDisplayDescriptor descriptor)
-            throws InvalidTypeException {
-        if (descriptor.seriesValueIndex != -1) {
-            if (descriptor.includePid == false) {
-                return event.getValueAsString(descriptor.seriesValueIndex);
-            } else {
-                return String.format("%1$s (%2$d)",
-                        event.getValueAsString(descriptor.seriesValueIndex), event.pid);
-            }
-        }
-
-        return Integer.toString(event.pid);
-    }
-
-    /**
      * Fills a list with {@link OccurrenceDisplayDescriptor} (or a subclass of it) from another
      * list if they are configured to display the {@link EventContainer}
-     * @param event the event container
+     *
+     * @param event    the event container
      * @param fullList the list with all the descriptors.
-     * @param outList the list to fill.
+     * @param outList  the list to fill.
      */
     @SuppressWarnings("unchecked")
     private void getDescriptors(EventContainer event,
@@ -1348,200 +866,21 @@ final class EventDisplay {
             }
         }
     }
-    
-    /**
-     * Adds an {@link EventContainer} to the log.
-     * @param event the event.
-     * @param logParser the log parser.
-     */
-    private void addToLog(EventContainer event, EventLogParser logParser) {
-        ScrollBar bar = mLogTable.getVerticalBar();
-        boolean scroll = bar.getMaximum() == bar.getSelection() + bar.getThumb();
-        
-        // get the date.
-        Calendar c = Calendar.getInstance();
-        long msec = (long)event.sec * 1000L;
-        c.setTimeInMillis(msec);
-        
-        // convert the time into a string
-        String date = String.format("%1$tF %1$tT", c);
-
-        String eventName = logParser.getTagMap().get(event.mTag);
-        String pidName = Integer.toString(event.pid);
-        
-        // get the value description
-        EventValueDescription[] valueDescription = logParser.getEventInfoMap().get(event.mTag);
-        if (valueDescription != null) {
-            for (int i = 0 ; i < valueDescription.length ; i++) {
-                EventValueDescription description = valueDescription[i];
-                try {
-                    String value = event.getValueAsString(i);
-    
-                    logValue(date, pidName, eventName, description.getName(), value,
-                            description.getEventValueType(), description.getValueType());
-                } catch (InvalidTypeException e) {
-                    logValue(date, pidName, eventName, description.getName(), e.getMessage(),
-                            description.getEventValueType(), description.getValueType());
-                }
-            }
-            
-            // scroll if needed, by showing the last item
-            if (scroll) {
-                int itemCount = mLogTable.getItemCount();
-                if (itemCount > 0) {
-                    mLogTable.showItem(mLogTable.getItem(itemCount-1));
-                }
-            }
-        }
-    }
-
-    /**
-     * Adds an {@link EventContainer} to the log. Only add the values/occurrences defined by
-     * the list of descriptors. If an event is configured to be displayed by value and occurrence,
-     * only the values are displayed (as they mark an event occurrence anyway).
-     * <p/>This method is only called when at least one of the descriptor list is non empty.
-     * @param event
-     * @param logParser
-     * @param valueDescriptors
-     * @param occurrenceDescriptors
-     */
-    private void addToLog(EventContainer event, EventLogParser logParser,
-            ArrayList<ValueDisplayDescriptor> valueDescriptors,
-            ArrayList<OccurrenceDisplayDescriptor> occurrenceDescriptors) {
-        ScrollBar bar = mLogTable.getVerticalBar();
-        boolean scroll = bar.getMaximum() == bar.getSelection() + bar.getThumb();
-
-        // get the date.
-        Calendar c = Calendar.getInstance();
-        long msec = (long)event.sec * 1000L;
-        c.setTimeInMillis(msec);
-        
-        // convert the time into a string
-        String date = String.format("%1$tF %1$tT", c);
-
-        String eventName = logParser.getTagMap().get(event.mTag);
-        String pidName = Integer.toString(event.pid);
-
-        if (valueDescriptors.size() > 0) {
-            for (ValueDisplayDescriptor descriptor : valueDescriptors) {
-                logDescriptor(event, descriptor, date, pidName, eventName, logParser);
-            }
-        } else {
-            // we display the event. Since the StringBuilder contains the header (date, event name,
-            // pid) at this point, there isn't anything else to display.
-        }
-        
-        // scroll if needed, by showing the last item
-        if (scroll) {
-            int itemCount = mLogTable.getItemCount();
-            if (itemCount > 0) {
-                mLogTable.showItem(mLogTable.getItem(itemCount-1));
-            }
-        }
-    }
-
-    /**
-     * Logs a value from an {@link EventContainer} as defined by the {@link ValueDisplayDescriptor}.
-     * @param event the EventContainer
-     * @param descriptor the ValueDisplayDescriptor defining which value to display.
-     * @param date the date of the event in a string.
-     * @param pidName 
-     * @param eventName
-     * @param logParser 
-     */
-    private void logDescriptor(EventContainer event, ValueDisplayDescriptor descriptor,
-            String date, String pidName, String eventName, EventLogParser logParser) {
-        
-        String value; 
-        try {
-            value = event.getValueAsString(descriptor.valueIndex);
-        } catch (InvalidTypeException e) {
-            value = e.getMessage();
-        }
-        
-        EventValueDescription[] values = logParser.getEventInfoMap().get(event.mTag);
-
-        EventValueDescription valueDescription = values[descriptor.valueIndex];
-        
-        logValue(date, pidName, eventName, descriptor.valueName, value,
-                valueDescription.getEventValueType(), valueDescription.getValueType());
-    }
-    
-    /**
-     * Logs a value in the ui.
-     * @param date
-     * @param pid
-     * @param event
-     * @param valueName
-     * @param value
-     * @param eventValueType
-     * @param valueType
-     */
-    private void logValue(String date, String pid, String event, String valueName,
-            String value, EventValueType eventValueType, ValueType valueType) {
-        
-        TableItem item = new TableItem(mLogTable, SWT.NONE);
-        item.setText(0, date);
-        item.setText(1, pid);
-        item.setText(2, event);
-        item.setText(3, valueName);
-        item.setText(4, value);
-        
-        String type;
-        if (valueType != ValueType.NOT_APPLICABLE) {
-            type = String.format("%1$s, %2$s", eventValueType.toString(), valueType.toString());
-        } else {
-            type = eventValueType.toString();
-        }
-
-        item.setText(5, type);
-    }
 
     /**
-     * Show the current value(s) of an {@link EventContainer}. The values to show are defined by
-     * the {@link ValueDisplayDescriptor}s and {@link OccurrenceDisplayDescriptor}s passed in the
-     * two lists.
-     * @param event
-     * @param logParser
-     * @param valueDescriptors
-     * @param occurrenceDescriptors
-     */
-    private void showCurrent(EventContainer event, EventLogParser logParser,
-            ArrayList<ValueDisplayDescriptor> valueDescriptors,
-            ArrayList<OccurrenceDisplayDescriptor> occurrenceDescriptors) {
-        // TODO Auto-generated method stub
-    }
-
-    // Values from data/etc/event-log-tags
-    final int EVENT_SYNC = 2720;
-    final int EVENT_TICKLE = 2742;
-    final int EVENT_SYNC_DETAILS = 2743;
-
-    /**
-     * Filters the {@link EventContainer}, and fills two list of {@link ValueDisplayDescriptor}
-     * and {@link OccurrenceDisplayDescriptor} configured to display the event.
+     * Filters the {@link com.android.ddmlib.log.EventContainer}, and fills two list of {@link com.android.ddmuilib.log.event.EventDisplay.ValueDisplayDescriptor}
+     * and {@link com.android.ddmuilib.log.event.EventDisplay.OccurrenceDisplayDescriptor} configured to display the event.
+     *
      * @param event
      * @param valueDescriptors
      * @param occurrenceDescriptors
      * @return true if the event should be displayed.
      */
-    private boolean filterEvent(EventContainer event,
+
+    protected boolean filterEvent(EventContainer event,
             ArrayList<ValueDisplayDescriptor> valueDescriptors,
             ArrayList<OccurrenceDisplayDescriptor> occurrenceDescriptors) {
 
-        if (mDisplayType == DISPLAY_TYPE_LOG_ALL) {
-            return true;
-        }
-
-        if (mDisplayType == DISPLAY_TYPE_SYNC || mDisplayType == DISPLAY_TYPE_SYNC_HIST) {
-            if (event.mTag == EVENT_SYNC || event.mTag == EVENT_TICKLE ||
-                    event.mTag == EVENT_SYNC_DETAILS) {
-                return true;
-            } else {
-                return false;
-            }
-        }
-
         // test the pid first (if needed)
         if (mPidFiltering && mPidFilterList != null) {
             boolean found = false;
@@ -1551,7 +890,7 @@ final class EventDisplay {
                     break;
                 }
             }
-            
+
             if (found == false) {
                 return false;
             }
@@ -1569,7 +908,8 @@ final class EventDisplay {
      * Checks all the {@link ValueDisplayDescriptor} for similarity.
      * If all the event values are from the same tag, the method will return EVENT_CHECK_SAME_TAG.
      * If all the event/value are the same, the method will return EVENT_CHECK_SAME_VALUE
-     * @return
+     *
+     * @return flag as described above
      */
     private int checkDescriptors() {
         if (mValueDescriptors.size() < 2) {
@@ -1598,39 +938,8 @@ final class EventDisplay {
         if (index == -1) {
             return EVENT_CHECK_SAME_TAG;
         }
-        
-        return EVENT_CHECK_SAME_VALUE;
-    }
-    
-    /**
-     * Returns a meaningful chart title based on the value of {@link #mValueDescriptorCheck}.
-     * @param logParser the logParser.
-     * @return the chart title.
-     */
-    private String getChartTitle(EventLogParser logParser) {
-        if (mValueDescriptors.size() > 0) {
-            String chartDesc = null;
-            switch (mValueDescriptorCheck) {
-                case EVENT_CHECK_SAME_TAG:
-                    if (logParser != null) {
-                        chartDesc = logParser.getTagMap().get(mValueDescriptors.get(0).eventTag);
-                    }
-                    break;
-                case EVENT_CHECK_SAME_VALUE:
-                    if (logParser != null) {
-                        chartDesc = String.format("%1$s / %2$s",
-                                logParser.getTagMap().get(mValueDescriptors.get(0).eventTag),
-                                mValueDescriptors.get(0).valueName);
-                    }
-                    break;
-            }
-            
-            if (chartDesc != null) {
-                return String.format("%1$s - %2$s", mName, chartDesc);
-            }
-        }
 
-        return mName;
+        return EVENT_CHECK_SAME_VALUE;
     }
 
     /**
@@ -1642,17 +951,19 @@ final class EventDisplay {
 
     /**
      * Sets the time limit on the charts.
+     *
      * @param timeLimit the time limit in seconds.
      */
     void setChartTimeLimit(long timeLimit) {
         mMaximumChartItemAge = timeLimit;
     }
-    
+
     long getChartTimeLimit() {
         return mMaximumChartItemAge;
     }
 
     /**
+     * m
      * Resets the histogram width
      */
     void resetHistWidth() {
@@ -1661,150 +972,24 @@ final class EventDisplay {
 
     /**
      * Sets the histogram width
+     *
      * @param histWidth the width in hours
      */
     void setHistWidth(long histWidth) {
         mHistWidth = histWidth;
     }
-    
+
     long getHistWidth() {
         return mHistWidth;
     }
 
-    // Implementation of the Sync display
-    // TODO: DISPLAY_TYPE_LOG, DISPLAY_TYPE_GRAPH, and DISPLAY_TYPE_SYNC should be subclasses
-    // of EventDisplay.java
-
-    private static final int CALENDAR = 0;
-    private static final int GMAIL = 1;
-    private static final int FEEDS = 2;
-    private static final int CONTACTS = 3;
-    private static final int ERRORS = 4;
-    private static final int NUM_AUTHS = (CONTACTS+1);
-    private static final String AUTH_NAMES[] = {"Calendar", "Gmail", "Feeds", "Contacts", "Errors"};
-    private static final Color AUTH_COLORS[] = {Color.MAGENTA,  Color.GREEN, Color.BLUE, Color.ORANGE, Color.RED};
-
-    // Information to graph for each authority
-    private TimePeriodValues mDatasetsSync[];
-    private List<String> mTooltipsSync[];
-    private CustomXYToolTipGenerator mTooltipGenerators[];
-    private TimeSeries mDatasetsSyncTickle[];
-
-    // Dataset of error events to graph
-    private TimeSeries mDatasetError;
-
-    /**
-     * Initialize the Plot and series data for the sync display.
-     */
-    void initSyncDisplay() {
-        XYPlot xyPlot = mChart.getXYPlot();
-
-        XYBarRenderer br = new XYBarRenderer();
-        mDatasetsSync = new TimePeriodValues[NUM_AUTHS];
-        mTooltipsSync = new List[NUM_AUTHS];
-        mTooltipGenerators = new CustomXYToolTipGenerator[NUM_AUTHS];
-        mLastDetails = "";
-
-        TimePeriodValuesCollection tpvc = new TimePeriodValuesCollection();
-        xyPlot.setDataset(tpvc);
-        xyPlot.setRenderer(0, br);
-
-        XYLineAndShapeRenderer ls = new XYLineAndShapeRenderer();
-        ls.setBaseLinesVisible(false);
-        mDatasetsSyncTickle = new TimeSeries[NUM_AUTHS];
-        TimeSeriesCollection tsc = new TimeSeriesCollection();
-        xyPlot.setDataset(1, tsc);
-        xyPlot.setRenderer(1, ls);
-
-        mDatasetError = new TimeSeries("Errors", FixedMillisecond.class);
-        xyPlot.setDataset(2, new TimeSeriesCollection(mDatasetError));
-        XYLineAndShapeRenderer errls = new XYLineAndShapeRenderer();
-        errls.setBaseLinesVisible(false);
-        errls.setSeriesPaint(0, Color.RED);        
-        xyPlot.setRenderer(2, errls);
-
-        for (int i = 0; i < NUM_AUTHS; i++) {
-            br.setSeriesPaint(i, AUTH_COLORS[i]);
-            ls.setSeriesPaint(i, AUTH_COLORS[i]);
-            mDatasetsSync[i] = new TimePeriodValues(AUTH_NAMES[i]);
-            tpvc.addSeries(mDatasetsSync[i]);
-            mTooltipsSync[i] = new ArrayList<String>();
-            mTooltipGenerators[i] = new CustomXYToolTipGenerator();
-            br.setSeriesToolTipGenerator(i, mTooltipGenerators[i]);
-            mTooltipGenerators[i].addToolTipSeries(mTooltipsSync[i]);
-
-            mDatasetsSyncTickle[i] = new TimeSeries(AUTH_NAMES[i] + " tickle", FixedMillisecond.class);
-            tsc.addSeries(mDatasetsSyncTickle[i]);
-            ls.setSeriesShape(i, ShapeUtilities.createUpTriangle(2.5f));
-        }
-    }
-
-    // State information while processing the event stream
-    private int mLastState; // 0 if event started, 1 if event stopped
-    private long mLastStartTime; // ms
-    private long mLastStopTime; //ms
-    private String mLastDetails;
-    private int mLastEvent; // server, poll, etc
-
-    /**
-     * Updates the display with a new event.  This is the main entry point for
-     * each event.  This method has the logic to tie together the start event,
-     * stop event, and details event into one graph item.  Note that the details
-     * can happen before or after the stop event.
-     * @param event The event
-     */
-    private void updateSyncDisplay(EventContainer event) {
-        try {
-            if (event.mTag == EVENT_SYNC) {
-                int state = Integer.parseInt(event.getValueAsString(1));
-                if (state == 0) { // start
-                    mLastStartTime = (long)event.sec * 1000L + (event.nsec / 1000000L);
-                    mLastState = 0;
-                    mLastEvent = Integer.parseInt(event.getValueAsString(2));
-                    mLastDetails = "";
-                } else if (state == 1) { // stop
-                    if (mLastState == 0) {
-                        mLastStopTime = (long)event.sec * 1000L + (event.nsec / 1000000L);
-                        if (mLastStartTime == 0) {
-                            // Log starts with a stop event
-                            mLastStartTime = mLastStopTime;
-                        }
-                        addEvent(event);
-                        mLastState = 1;
-                    }
-                }
-            } else if (event.mTag == EVENT_TICKLE) {
-                int auth = getAuth(event.getValueAsString(0));
-                if (auth >= 0) {
-                    long msec = (long)event.sec * 1000L + (event.nsec / 1000000L);
-                    mDatasetsSyncTickle[auth].addOrUpdate(new FixedMillisecond(msec), -1);
-                }
-            } else if (event.mTag == EVENT_SYNC_DETAILS) {
-                int auth = getAuth(event.getValueAsString(0));
-                mLastDetails = event.getValueAsString(3);
-                if (mLastState != 0) { // Not inside event
-                    long updateTime = (long)event.sec * 1000L + (event.nsec / 1000000L);
-                    if (updateTime - mLastStopTime <= 250) {
-                        // Got details within 250ms after event, so delete and re-insert
-                        // Details later than 250ms (arbitrary) are discarded as probably
-                        // unrelated.
-                        int lastItem = mDatasetsSync[auth].getItemCount();
-                        mDatasetsSync[auth].delete(lastItem-1, lastItem-1);
-                        mTooltipsSync[auth].remove(lastItem-1);
-                        addEvent(event);
-                    }
-                }
-            }
-        } catch (InvalidTypeException e) {
-        }
-    }
-
     /**
      * Convert authority name to auth number.
+     *
      * @param authname "calendar", etc.
      * @return number series number associated with the authority
      */
-    private int getAuth(String authname) throws InvalidTypeException {
+    protected int getAuth(String authname) throws InvalidTypeException {
         if ("calendar".equals(authname) || "cl".equals(authname)) {
             return CALENDAR;
         } else if ("contacts".equals(authname) || "cp".equals(authname)) {
@@ -1821,283 +1006,4 @@ final class EventDisplay {
             throw new InvalidTypeException("Unknown authname " + authname);
         }
     }
-
-    /**
-     * Generate the height for an event.
-     * Height is somewhat arbitrarily the count of "things" that happened
-     * during the sync.
-     * When network traffic measurements are available, code should be modified
-     * to use that instead.
-     * @param details The details string associated with the event
-     * @return The height in arbirary units (0-100)
-     */
-    private int getHeightFromDetails(String details) {
-        if (details == null) {
-            return 1; // Arbitrary
-        }
-        int total = 0;
-        String parts[] = details.split("[a-zA-Z]");
-        for (String part : parts) {
-            if ("".equals(part)) continue;
-            total += Integer.parseInt(part);
-        }
-        if (total == 0) {
-            total = 1;
-        }
-        return total;
-    }
-
-    /**
-     * Generates the tooltips text for an event.
-     * This method decodes the cryptic details string.
-     * @param auth The authority associated with the event
-     * @param details The details string
-     * @param eventSource server, poll, etc.
-     * @return The text to display in the tooltips
-     */
-    private String getTextFromDetails(int auth, String details, int eventSource) {
-
-        StringBuffer sb = new StringBuffer();
-        sb.append(AUTH_NAMES[auth]).append(": \n");
-
-        Scanner scanner = new Scanner(details);
-        Pattern charPat = Pattern.compile("[a-zA-Z]");
-        Pattern numPat = Pattern.compile("[0-9]+");
-        while (scanner.hasNext()) {
-            String key = scanner.findInLine(charPat);
-            int val = Integer.parseInt(scanner.findInLine(numPat));
-            if (auth == GMAIL && "M".equals(key)) {
-                sb.append("messages from server: ").append(val).append("\n");
-            } else if (auth == GMAIL && "L".equals(key)) {
-                sb.append("labels from server: ").append(val).append("\n");
-            } else if (auth == GMAIL && "C".equals(key)) {
-                sb.append("check conversation requests from server: ").append(val).append("\n");
-            } else if (auth == GMAIL && "A".equals(key)) {
-                sb.append("attachments from server: ").append(val).append("\n");
-            } else if (auth == GMAIL && "U".equals(key)) {
-                sb.append("op updates from server: ").append(val).append("\n");
-            } else if (auth == GMAIL && "u".equals(key)) {
-                sb.append("op updates to server: ").append(val).append("\n");
-            } else if (auth == GMAIL && "S".equals(key)) {
-                sb.append("send/receive cycles: ").append(val).append("\n");
-            } else if ("Q".equals(key)) {
-                sb.append("queries to server: ").append(val).append("\n");
-            } else if ("E".equals(key)) {
-                sb.append("entries from server: ").append(val).append("\n");
-            } else if ("u".equals(key)) {
-                sb.append("updates from client: ").append(val).append("\n");
-            } else if ("i".equals(key)) {
-                sb.append("inserts from client: ").append(val).append("\n");
-            } else if ("d".equals(key)) {
-                sb.append("deletes from client: ").append(val).append("\n");
-            } else if ("f".equals(key)) {
-                sb.append("full sync requested\n");
-            } else if ("r".equals(key)) {
-                sb.append("partial sync unavailable\n");
-            } else if ("X".equals(key)) {
-                sb.append("hard error\n");
-            } else if ("e".equals(key)) {
-                sb.append("number of parse exceptions: ").append(val).append("\n");
-            } else if ("c".equals(key)) {
-                sb.append("number of conflicts: ").append(val).append("\n");
-            } else if ("a".equals(key)) {
-                sb.append("number of auth exceptions: ").append(val).append("\n");
-            } else if ("D".equals(key)) {
-                sb.append("too many deletions\n");
-            } else if ("R".equals(key)) {
-                sb.append("too many retries: ").append(val).append("\n");
-            } else if ("b".equals(key)) {
-                sb.append("database error\n");
-            } else if ("x".equals(key)) {
-                sb.append("soft error\n");
-            } else if ("l".equals(key)) {
-                sb.append("sync already in progress\n");
-            } else if ("I".equals(key)) {
-                sb.append("io exception\n");
-            } else if (auth == CONTACTS && "p".equals(key)) {
-                sb.append("photos uploaded from client: ").append(val).append("\n");
-            } else if (auth == CONTACTS && "P".equals(key)) {
-                sb.append("photos downloaded from server: ").append(val).append("\n");
-            } else if (auth == CALENDAR && "F".equals(key)) {
-                sb.append("server refresh\n");
-            } else if (auth == CALENDAR && "s".equals(key)) {
-                sb.append("server diffs fetched\n");
-            } else {
-                sb.append(key).append("=").append(val);
-            }
-        }
-        if (eventSource == 0) {
-            sb.append("(server)");
-        } else if (eventSource == 1) {
-            sb.append("(local)");
-        } else if (eventSource == 2) {
-            sb.append("(poll)");
-        } else if (eventSource == 3) {
-            sb.append("(user)");
-        }
-        return sb.toString();
-    }
-
-    /**
-     * Helper to add an event to the data series.
-     * Also updates error series if appropriate (x or X in details).
-     * @param event The event
-     */
-    private void addEvent(EventContainer event) {
-        try {
-            int auth = getAuth(event.getValueAsString(0));
-            double height = getHeightFromDetails(mLastDetails);
-            height = height / (mLastStopTime - mLastStartTime + 1) * 10000;
-            if (height > 30) {
-                height = 30;
-            }
-            mDatasetsSync[auth].add(new SimpleTimePeriod(mLastStartTime, mLastStopTime), height);
-            mTooltipsSync[auth].add(getTextFromDetails(auth, mLastDetails,
-                    mLastEvent));
-            mTooltipGenerators[auth].addToolTipSeries(mTooltipsSync[auth]);
-            if (mLastDetails.indexOf('x') >= 0 || mLastDetails.indexOf('X') >= 0) {
-                long msec = (long)event.sec * 1000L + (event.nsec / 1000000L);
-                mDatasetError.addOrUpdate(new FixedMillisecond(msec), -1);
-            }
-        } catch (InvalidTypeException e) {
-            e.printStackTrace();
-        }
-    }
-
-    // Implementation of the Sync Histogram display
-
-    // Information to graph for each authority
-    private TimePeriodValues mDatasetsSyncHist[];
-
-    /**
-     * Initialize the Plot and series data for the sync display.
-     */
-    void initSyncHistDisplay() {
-        XYPlot xyPlot = mChart.getXYPlot();
-
-        AbstractXYItemRenderer br = new XYBarRenderer();
-        mDatasetsSyncHist = new TimePeriodValues[NUM_AUTHS+1];
-        mLastDetails = "";
-        mTimePeriodMap = new HashMap[NUM_AUTHS + 1];
-
-        TimePeriodValuesCollection tpvc = new TimePeriodValuesCollection();
-        xyPlot.setDataset(tpvc);
-        xyPlot.setRenderer(br);
-
-        for (int i = 0; i < NUM_AUTHS + 1; i++) {
-            br.setSeriesPaint(i, AUTH_COLORS[i]);
-            mDatasetsSyncHist[i] = new TimePeriodValues(AUTH_NAMES[i]);
-            tpvc.addSeries(mDatasetsSyncHist[i]);
-            mTimePeriodMap[i] = new HashMap<SimpleTimePeriod, Integer>();
-
-        }
-    }
-
-    /**
-     * Updates the display with a new event.  This is the main entry point for
-     * each event.  This method has the logic to tie together the start event,
-     * stop event, and details event into one graph item.  Note that the details
-     * can happen before or after the stop event.
-     * @param event The event
-     */
-    private void updateSyncHistDisplay(EventContainer event) {
-        try {
-            if (event.mTag == EVENT_SYNC) {
-                int state = Integer.parseInt(event.getValueAsString(1));
-                if (state == 0) { // start
-                    mLastStartTime = (long)event.sec * 1000L + (event.nsec / 1000000L);
-                    mLastState = 0;
-                    mLastEvent = Integer.parseInt(event.getValueAsString(2));
-                    mLastDetails = "";
-                } else if (state == 1) { // stop
-                    if (mLastState == 0) {
-                        mLastStopTime = (long)event.sec * 1000L + (event.nsec / 1000000L);
-                        if (mLastStartTime == 0) {
-                            // Log starts with a stop event
-                            mLastStartTime = mLastStopTime;
-                        }
-                        int auth = getAuth(event.getValueAsString(0));
-                        if (mLastDetails.indexOf('x') >= 0 || mLastDetails.indexOf('X') >= 0) {
-                            auth = ERRORS;
-                        }
-                        double delta = (mLastStopTime - mLastStartTime) * 100. / 1000 / 3600; // Percent of hour
-                        addHistEvent(event, auth, delta);
-                        mLastState = 1;
-                    }
-                }
-            } else if (event.mTag == EVENT_SYNC_DETAILS) {
-                int auth = getAuth(event.getValueAsString(0));
-                mLastDetails = event.getValueAsString(3);
-                if (mLastState != 0) { // Not inside event
-                    long updateTime = (long)event.sec * 1000L + (event.nsec / 1000000L);
-                    if (updateTime - mLastStopTime <= 250) {
-                        // Got details within 250ms after event, so delete and re-insert
-                        // Details later than 250ms (arbitrary) are discarded as probably
-                        // unrelated.
-                        //int lastItem = mDatasetsSync[auth].getItemCount();
-                        //addHistEvent(event);
-                        if (mLastDetails.indexOf('x') >= 0 || mLastDetails.indexOf('X') >= 0) {
-                            // Item turns out to be in error, so transfer time from old auth to error.
-
-                            double delta = (mLastStopTime - mLastStartTime) * 100. / 1000 / 3600; // Percent of hour
-                            addHistEvent(event, auth, -delta);
-                            addHistEvent(event, ERRORS, delta);
-                        }
-                    }
-                }
-            }
-        } catch (InvalidTypeException e) {
-        }
-    }
-
-    /**
-     * Helper to add an event to the data series.
-     * Also updates error series if appropriate (x or X in details).
-     * @param event The event
-     * @param auth
-     * @param value
-     */
-    private void addHistEvent(EventContainer event, int auth, double value) {
-        SimpleTimePeriod hour = getTimePeriod(mLastStopTime, mHistWidth);
-
-        // Loop over all datasets to do the stacking.
-        for (int i = auth; i <= ERRORS; i++) {
-            addToPeriod(mDatasetsSyncHist, i, hour, value);
-        }
-    }
-
-    Map<SimpleTimePeriod, Integer> mTimePeriodMap[];
-
-    private void addToPeriod(TimePeriodValues tpv[], int auth, SimpleTimePeriod period, double value) {
-        int index;
-        if (mTimePeriodMap[auth].containsKey(period)) {
-            index = mTimePeriodMap[auth].get(period);
-            double oldValue = tpv[auth].getValue(index).doubleValue();
-            tpv[auth].update(index, oldValue + value);
-        } else {
-            index = tpv[auth].getItemCount();
-            mTimePeriodMap[auth].put(period, index);
-            tpv[auth].add(period, value);
-        }
-    }
-
-    /**
-     * Creates a multiple-hour time period for the histogram.
-     * @param time Time in milliseconds.
-     * @param numHoursWide: should divide into a day.
-     * @return SimpleTimePeriod covering the number of hours and containing time.
-     */
-    private SimpleTimePeriod getTimePeriod(long time, long numHoursWide) {
-        Date date = new Date(time);
-        TimeZone zone = RegularTimePeriod.DEFAULT_TIME_ZONE;
-        Calendar calendar = Calendar.getInstance(zone);
-        calendar.setTime(date);
-        long hoursOfYear = calendar.get(Calendar.HOUR_OF_DAY) + calendar.get(Calendar.DAY_OF_YEAR) * 24;
-        int year = calendar.get(Calendar.YEAR);
-        hoursOfYear = (hoursOfYear / numHoursWide) * numHoursWide;
-        calendar.clear();
-        calendar.set(year, 0, 1, 0, 0); // Jan 1
-        long start = calendar.getTimeInMillis() + hoursOfYear * 3600 * 1000;
-        return new SimpleTimePeriod(start, start + numHoursWide * 3600 * 1000);
-    }
 }
index 94f04d7..b9daa41 100644 (file)
@@ -23,7 +23,6 @@ import com.android.ddmuilib.DdmUiPreferences;
 import com.android.ddmuilib.IImageLoader;
 import com.android.ddmuilib.log.event.EventDisplay.OccurrenceDisplayDescriptor;
 import com.android.ddmuilib.log.event.EventDisplay.ValueDisplayDescriptor;
-
 import org.eclipse.jface.preference.IPreferenceStore;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.ModifyEvent;
@@ -444,10 +443,13 @@ class EventDisplayOptions  extends Dialog {
             @Override
             public void widgetSelected(SelectionEvent e) {
                 EventDisplay eventDisplay = getCurrentEventDisplay();
-                if (eventDisplay != null) {
+                if (eventDisplay != null && eventDisplay.getDisplayType() != mDisplayTypeCombo.getSelectionIndex()) {
+                    /* Replace the EventDisplay object with a different subclass */
                     setModified();
-                    eventDisplay.setDisplayType(mDisplayTypeCombo.getSelectionIndex());
-                    fillUiWith(eventDisplay);
+                    String name = eventDisplay.getName();
+                    EventDisplay newEventDisplay = EventDisplay.eventDisplayFactory(mDisplayTypeCombo.getSelectionIndex(), name);
+                    setCurrentEventDisplay(newEventDisplay);
+                    fillUiWith(newEventDisplay);
                 }
             }
         });
@@ -693,7 +695,7 @@ class EventDisplayOptions  extends Dialog {
     
     private void duplicateEventDisplay(ArrayList<EventDisplay> displayList) {
         for (EventDisplay eventDisplay : displayList) {
-            mDisplayList.add(new EventDisplay(eventDisplay));
+            mDisplayList.add(EventDisplay.clone(eventDisplay));
         }
     }
     
@@ -744,7 +746,7 @@ class EventDisplayOptions  extends Dialog {
         
         String name = String.format("display %1$d", count + 1);
         
-        EventDisplay eventDisplay = new EventDisplay(name);
+        EventDisplay eventDisplay = EventDisplay.eventDisplayFactory(0 /* type*/, name);
         
         mDisplayList.add(eventDisplay);
         mEventDisplayList.add(name);
@@ -779,6 +781,13 @@ class EventDisplayOptions  extends Dialog {
         
         return null;
     }
+
+    private void setCurrentEventDisplay(EventDisplay eventDisplay) {
+        int selection = mEventDisplayList.getSelectionIndex();
+        if (selection != -1) {
+            mDisplayList.set(selection, eventDisplay);
+        }
+    }
     
     private void handleEventDisplaySelection() {
         EventDisplay eventDisplay = getCurrentEventDisplay();
index 2ace78a..a1303f6 100644 (file)
@@ -38,7 +38,7 @@ public class EventLogImporter {
         if (top == null) {
             throw new FileNotFoundException();
         }
-        final String tagFile = top + "/data/etc/event-log-tags";
+        final String tagFile = top + "/system/core/logcat/event-log-tags";
         BufferedReader tagReader = new BufferedReader(
                 new InputStreamReader(new FileInputStream(tagFile)));
         BufferedReader eventReader = new BufferedReader(
index 6743246..82bcea8 100644 (file)
@@ -181,7 +181,7 @@ public class ApkBuilder extends BaseBuilder {
             return mMakeFinalPackage;
         }
     }
-    
+
     /**
      * {@link IZipEntryFilter} to filter out everything that is not a standard java resources.
      * <p/>Used in {@link SignedJarBuilder#writeZip(java.io.InputStream, IZipEntryFilter)} when
@@ -215,6 +215,7 @@ public class ApkBuilder extends BaseBuilder {
 
         // First thing we do is go through the resource delta to not
         // lose it if we have to abort the build for any reason.
+        ApkDeltaVisitor dv = null;
         if (kind == FULL_BUILD) {
             AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
                     Messages.Start_Full_Apk_Build);
@@ -233,22 +234,13 @@ public class ApkBuilder extends BaseBuilder {
                 mConvertToDex = true;
                 mBuildFinalPackage = true;
             } else {
-                ApkDeltaVisitor dv = new ApkDeltaVisitor(this, sourceList, outputFolder);
+                dv = new ApkDeltaVisitor(this, sourceList, outputFolder);
                 delta.accept(dv);
 
                 // save the state
                 mPackageResources |= dv.getPackageResources();
                 mConvertToDex |= dv.getConvertToDex();
                 mBuildFinalPackage |= dv.getMakeFinalPackage();
-
-                if (dv.mXmlError) {
-                    AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
-                    Messages.Xml_Error);
-
-                    // if there was some XML errors, we just return w/o doing
-                    // anything since we've put some markers in the files anyway
-                    return referencedProjects;
-                }
             }
 
             // also go through the delta for all the referenced projects, until we are forced to
@@ -258,13 +250,13 @@ public class ApkBuilder extends BaseBuilder {
                 IJavaProject referencedJavaProject = referencedJavaProjects[i];
                 delta = getDelta(referencedJavaProject.getProject());
                 if (delta != null) {
-                    ReferencedProjectDeltaVisitor dv = new ReferencedProjectDeltaVisitor(
+                    ReferencedProjectDeltaVisitor refProjectDv = new ReferencedProjectDeltaVisitor(
                             referencedJavaProject);
-                    delta.accept(dv);
+                    delta.accept(refProjectDv);
 
                     // save the state
-                    mConvertToDex |= dv.needDexConvertion();
-                    mBuildFinalPackage |= dv.needMakeFinalPackage();
+                    mConvertToDex |= refProjectDv.needDexConvertion();
+                    mBuildFinalPackage |= refProjectDv.needMakeFinalPackage();
                 }
             }
         }
@@ -307,29 +299,14 @@ public class ApkBuilder extends BaseBuilder {
 
         // At this point, we can abort the build if we have to, as we have computed
         // our resource delta and stored the result.
+        abortOnBadSetup(javaProject);
         
-        // check if we have finished loading the SDK.
-        if (AdtPlugin.getDefault().getSdkLoadStatus(javaProject) != LoadStatus.LOADED) {
-            // we exit silently
-            return referencedProjects;
-        }
-
-        // Now check the compiler compliance level, not displaying the error
-        // message since this is not the first builder.
-        if (ProjectHelper.checkCompilerCompliance(getProject())
-                != ProjectHelper.COMPILER_COMPLIANCE_OK) {
-            return referencedProjects;
-        }
-
-        // now check if the project has problem marker already
-        if (ProjectHelper.hasError(project, true)) {
-            // we found a marker with error severity: we abort the build.
-            // Since this is going to happen every time we save a file while
-            // errors are remaining, we do not force the display of the console, which
-            // would, in most cases, show on top of the Problem view (which is more
-            // important in that case).
+        if (dv != null && dv.mXmlError) {
             AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
-                    Messages.Project_Has_Errors);
+            Messages.Xml_Error);
+
+            // if there was some XML errors, we just return w/o doing
+            // anything since we've put some markers in the files anyway
             return referencedProjects;
         }
 
index 534c123..04e9fbf 100644 (file)
@@ -19,10 +19,13 @@ package com.android.ide.eclipse.adt.build;
 import com.android.ide.eclipse.adt.AdtConstants;
 import com.android.ide.eclipse.adt.AdtPlugin;
 import com.android.ide.eclipse.adt.project.ProjectHelper;
+import com.android.ide.eclipse.adt.sdk.LoadStatus;
+import com.android.ide.eclipse.adt.sdk.Sdk;
 import com.android.ide.eclipse.common.AndroidConstants;
 import com.android.ide.eclipse.common.project.BaseProjectHelper;
 import com.android.ide.eclipse.common.project.XmlErrorHandler;
 import com.android.ide.eclipse.common.project.XmlErrorHandler.XmlErrorListener;
+import com.android.sdklib.IAndroidTarget;
 
 import org.eclipse.core.resources.IContainer;
 import org.eclipse.core.resources.IFile;
@@ -34,6 +37,8 @@ import org.eclipse.core.resources.IncrementalProjectBuilder;
 import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
 import org.eclipse.jdt.core.IClasspathEntry;
 import org.eclipse.jdt.core.IJavaProject;
 import org.eclipse.jdt.core.JavaCore;
@@ -841,4 +846,53 @@ abstract class BaseBuilder extends IncrementalProjectBuilder {
 
         return oslibraryList.toArray(new String[oslibraryList.size()]);
     }
+    
+    /**
+     * Aborts the build if the SDK/project setups are broken. This does not
+     * display any errors.
+     * 
+     * @param javaProject The {@link IJavaProject} being compiled.
+     * @throws CoreException
+     */
+    protected final void abortOnBadSetup(IJavaProject javaProject) throws CoreException {
+        // check if we have finished loading the SDK.
+        if (AdtPlugin.getDefault().getSdkLoadStatus(javaProject) != LoadStatus.LOADED) {
+            // we exit silently
+            stopBuild("SDK is not loaded yet");
+        }
+
+        // check the compiler compliance level.
+        if (ProjectHelper.checkCompilerCompliance(getProject()) !=
+                ProjectHelper.COMPILER_COMPLIANCE_OK) {
+            // we exit silently
+            stopBuild(Messages.Compiler_Compliance_Error);
+        }
+
+        // Check that the SDK directory has been setup.
+        String osSdkFolder = AdtPlugin.getOsSdkFolder();
+
+        if (osSdkFolder == null || osSdkFolder.length() == 0) {
+            stopBuild(Messages.No_SDK_Setup_Error);
+        }
+
+        IAndroidTarget projectTarget = Sdk.getCurrent().getTarget(javaProject.getProject());
+        if (projectTarget == null) {
+            // no target. error has been output by the container initializer:
+            // exit silently.
+            stopBuild("Project has no target");
+        }
+    }
+    
+    /**
+     * Throws an exception to cancel the build.
+     * 
+     * @param error the error message
+     * @param args the printf-style arguments to the error message.
+     * @throws CoreException
+     */
+    protected final void stopBuild(String error, Object... args) throws CoreException {
+        throw new CoreException(new Status(IStatus.CANCEL, AdtPlugin.PLUGIN_ID,
+                String.format(error, args)));
+    }
+
 }
index 9fc4348..fd4d772 100644 (file)
@@ -20,7 +20,6 @@ import com.android.ide.eclipse.adt.AdtConstants;
 import com.android.ide.eclipse.adt.AdtPlugin;
 import com.android.ide.eclipse.adt.project.FixLaunchConfig;
 import com.android.ide.eclipse.adt.project.ProjectHelper;
-import com.android.ide.eclipse.adt.sdk.LoadStatus;
 import com.android.ide.eclipse.adt.sdk.Sdk;
 import com.android.ide.eclipse.common.AndroidConstants;
 import com.android.ide.eclipse.common.project.AndroidManifestHelper;
@@ -224,7 +223,7 @@ public class PreCompilerBuilder extends BaseBuilder {
     public PreCompilerBuilder() {
         super();
     }
-
+    
     // build() returns a list of project from which this project depends for future compilation.
     @SuppressWarnings("unchecked") //$NON-NLS-1$
     @Override
@@ -274,15 +273,6 @@ public class PreCompilerBuilder extends BaseBuilder {
                     mergeAidlFileModifications(dv.getAidlToCompile(),
                             dv.getAidlToRemove());
                 }
-
-                // if there was some XML errors, we just return w/o doing
-                // anything since we've put some markers in the files anyway.
-                if (dv.mXmlError) {
-                    AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
-                            Messages.Xml_Error);
-
-                    return null;
-                }
                 
                 // get the java package from the visitor
                 javaPackage = dv.getManifestPackage();
@@ -295,39 +285,19 @@ public class PreCompilerBuilder extends BaseBuilder {
 
         // At this point we have stored what needs to be build, so we can
         // do some high level test and abort if needed.
-
-        // check if we have finished loading the SDK.
-        if (AdtPlugin.getDefault().getSdkLoadStatus(javaProject) != LoadStatus.LOADED) {
-            // we exit silently
-            return null;
-        }
-
-        // check the compiler compliance level, not displaying the error message
-        // since this is not the first builder.
-        if (ProjectHelper.checkCompilerCompliance(getProject())
-                != ProjectHelper.COMPILER_COMPLIANCE_OK) {
+        abortOnBadSetup(javaProject);
+        
+        // if there was some XML errors, we just return w/o doing
+        // anything since we've put some markers in the files anyway.
+        if (dv != null && dv.mXmlError) {
             AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
-                    Messages.Compiler_Compliance_Error);
-            return null;
-        }
-
-        // Check that the SDK directory has been setup.
-        String osSdkFolder = AdtPlugin.getOsSdkFolder();
+                    Messages.Xml_Error);
 
-        if (osSdkFolder == null || osSdkFolder.length() == 0) {
-            AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
-                    Messages.No_SDK_Setup_Error);
-            markProject(AdtConstants.MARKER_ADT, Messages.No_SDK_Setup_Error,
-                    IMarker.SEVERITY_ERROR);
-            return null;
-        }
-        
-        IAndroidTarget projectTarget = Sdk.getCurrent().getTarget(project);
-        if (projectTarget == null) {
-            // no target. error has been output by the container initializer: exit silently.
-            return null;
+            // This interrupts the build. The next builders will not run.
+            stopBuild(Messages.Xml_Error);
         }
 
+
         // get the manifest file
         IFile manifest = AndroidManifestHelper.getManifest(project);
 
@@ -336,7 +306,9 @@ public class PreCompilerBuilder extends BaseBuilder {
                     AndroidConstants.FN_ANDROID_MANIFEST);
             AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, msg);
             markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
-            return null;
+
+            // This interrupts the build. The next builders will not run.
+            stopBuild(msg);
         }
 
         // lets check the XML of the manifest first, if that hasn't been done by the
@@ -353,7 +325,9 @@ public class PreCompilerBuilder extends BaseBuilder {
                 String msg = String.format(Messages.s_Contains_Xml_Error,
                         AndroidConstants.FN_ANDROID_MANIFEST);
                 AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, msg);
-                return null;
+
+                // This interrupts the build. The next builders will not run.
+                stopBuild(msg);
             }
             
             // get the java package from the parser
@@ -366,7 +340,9 @@ public class PreCompilerBuilder extends BaseBuilder {
                     AndroidConstants.FN_ANDROID_MANIFEST);
             AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
                     msg);
-            return null;
+
+            // This interrupts the build. The next builders will not run.
+            stopBuild(msg);
         }
 
         // at this point we have the java package. We need to make sure it's not a different package
@@ -409,7 +385,8 @@ public class PreCompilerBuilder extends BaseBuilder {
                 AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, message);
 
                 // abort
-                return null;
+                // This interrupts the build. The next builders will not run.
+                stopBuild(message);
             }
 
 
@@ -430,6 +407,8 @@ public class PreCompilerBuilder extends BaseBuilder {
                 String osResPath = resLocation.toOSString();
                 String osManifestPath = manifestLocation.toOSString();
 
+                IAndroidTarget projectTarget = Sdk.getCurrent().getTarget(project);
+
                 // remove the aapt markers
                 removeMarkersFromFile(manifest, AndroidConstants.MARKER_AAPT);
                 removeMarkersFromContainer(resFolder, AndroidConstants.MARKER_AAPT);
@@ -517,20 +496,25 @@ public class PreCompilerBuilder extends BaseBuilder {
                                 Messages.AAPT_Error);
 
                         // abort if exec failed.
-                        return null;
+                        // This interrupts the build. The next builders will not run.
+                        stopBuild(Messages.AAPT_Error);
                     }
                 } catch (IOException e1) {
                     // something happen while executing the process,
                     // mark the project and exit
                     String msg = String.format(Messages.AAPT_Exec_Error, array.get(0));
                     markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
-                    return null;
+
+                    // This interrupts the build. The next builders will not run.
+                    stopBuild(msg);
                 } catch (InterruptedException e) {
                     // we got interrupted waiting for the process to end...
                     // mark the project and exit
                     String msg = String.format(Messages.AAPT_Exec_Error, array.get(0));
                     markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
-                    return null;
+
+                    // This interrupts the build. The next builders will not run.
+                    stopBuild(msg);
                 }
 
                 // if the return code was OK, we refresh the folder that
index 1219aac..1e7b77a 100644 (file)
@@ -19,16 +19,20 @@ package com.android.ide.eclipse.adt.build;
 import com.android.ide.eclipse.adt.AdtConstants;
 import com.android.ide.eclipse.adt.AdtPlugin;
 import com.android.ide.eclipse.adt.project.ProjectHelper;
+import com.android.ide.eclipse.adt.sdk.LoadStatus;
+import com.android.ide.eclipse.adt.sdk.Sdk;
 import com.android.ide.eclipse.common.AndroidConstants;
 import com.android.ide.eclipse.common.project.BaseProjectHelper;
+import com.android.sdklib.IAndroidTarget;
 
 import org.eclipse.core.resources.IFolder;
 import org.eclipse.core.resources.IMarker;
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.IResource;
-import org.eclipse.core.resources.IncrementalProjectBuilder;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaCore;
 
 import java.util.Map;
 
@@ -36,7 +40,7 @@ import java.util.Map;
  * Resource manager builder whose only purpose is to refresh the resource folder
  * so that the other builder use an up to date version.
  */
-public class ResourceManagerBuilder extends IncrementalProjectBuilder {
+public class ResourceManagerBuilder extends BaseBuilder {
 
     public static final String ID = "com.android.ide.eclipse.adt.ResourceManagerBuilder"; //$NON-NLS-1$
 
@@ -72,6 +76,38 @@ public class ResourceManagerBuilder extends IncrementalProjectBuilder {
             BaseProjectHelper.addMarker(project, AdtConstants.MARKER_ADT, errorMessage,
                     IMarker.SEVERITY_ERROR);
             AdtPlugin.printErrorToConsole(project, errorMessage);
+            
+            // interrupt the build. The next builders will not run.
+            stopBuild(errorMessage);
+        }
+
+        // Check that the SDK directory has been setup.
+        String osSdkFolder = AdtPlugin.getOsSdkFolder();
+
+        if (osSdkFolder == null || osSdkFolder.length() == 0) {
+            AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
+                    Messages.No_SDK_Setup_Error);
+            markProject(AdtConstants.MARKER_ADT, Messages.No_SDK_Setup_Error,
+                    IMarker.SEVERITY_ERROR);
+
+            // This interrupts the build. The next builders will not run.
+            stopBuild(Messages.No_SDK_Setup_Error);
+        }
+
+        // check if we have finished loading the SDK.
+        IJavaProject javaProject = JavaCore.create(project);
+        if (AdtPlugin.getDefault().getSdkLoadStatus(javaProject) != LoadStatus.LOADED) {
+            // we exit silently
+            // This interrupts the build. The next builders will not run.
+            stopBuild("SDK is not loaded yet");
+        }
+        
+        // check the project has a target
+        IAndroidTarget projectTarget = Sdk.getCurrent().getTarget(project);
+        if (projectTarget == null) {
+            // no target. marker has been set by the container initializer: exit silently.
+            // This interrupts the build. The next builders will not run.
+            stopBuild("Project has no target");
         }
 
         // Check the preference to be sure we are supposed to refresh
index 2b7d01d..87f902a 100644 (file)
@@ -35,8 +35,8 @@ import com.android.ide.eclipse.adt.sdk.Sdk;
 import com.android.ide.eclipse.common.project.AndroidManifestHelper;
 import com.android.sdklib.IAndroidTarget;
 import com.android.sdklib.SdkManager;
-import com.android.sdklib.vm.VmManager;
-import com.android.sdklib.vm.VmManager.VmInfo;
+import com.android.sdklib.avd.AvdManager;
+import com.android.sdklib.avd.AvdManager.AvdInfo;
 
 import org.eclipse.core.resources.IFile;
 import org.eclipse.core.resources.IProject;
@@ -74,7 +74,7 @@ import java.util.regex.Pattern;
 public final class AndroidLaunchController implements IDebugBridgeChangeListener,
         IDeviceChangeListener, IClientChangeListener {
     
-    private static final String FLAG_VM = "-vm"; //$NON-NLS-1$
+    private static final String FLAG_AVD = "-avd"; //$NON-NLS-1$
     private static final String FLAG_NETDELAY = "-netdelay"; //$NON-NLS-1$
     private static final String FLAG_NETSPEED = "-netspeed"; //$NON-NLS-1$
     private static final String FLAG_WIPE_DATA = "-wipe-data"; //$NON-NLS-1$
@@ -228,9 +228,9 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
         public boolean mNoBootAnim = LaunchConfigDelegate.DEFAULT_NO_BOOT_ANIM;
         
         /**
-         * Vm Name.
+         * AVD Name.
          */
-        public String mVmName = null;
+        public String mAvdName = null;
         
         public String mNetworkSpeed = EmulatorConfigTab.getSpeed(
                 LaunchConfigDelegate.DEFAULT_SPEED);
@@ -262,7 +262,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
             }
 
             try {
-                mVmName = config.getAttribute(LaunchConfigDelegate.ATTR_VM_NAME, mVmName);
+                mAvdName = config.getAttribute(LaunchConfigDelegate.ATTR_AVD_NAME, mAvdName);
             } catch (CoreException e) {
             }
 
@@ -531,8 +531,8 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
                 wc.setAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE,
                         LaunchConfigDelegate.DEFAULT_TARGET_MODE);
 
-                // default VM: None
-                wc.setAttribute(LaunchConfigDelegate.ATTR_VM_NAME, (String)null);
+                // default AVD: None
+                wc.setAttribute(LaunchConfigDelegate.ATTR_AVD_NAME, (String)null);
 
                 // set the default network speed
                 wc.setAttribute(LaunchConfigDelegate.ATTR_SPEED,
@@ -629,12 +629,12 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
 
         // get the SDK
         Sdk currentSdk = Sdk.getCurrent();
-        VmManager vmManager = currentSdk.getVmManager();
+        AvdManager avdManager = currentSdk.getAvdManager();
         
         // get the project target
         final IAndroidTarget projectTarget = currentSdk.getTarget(project);
         
-        // FIXME: check errors on missing sdk, vm manager, or project target.
+        // FIXME: check errors on missing sdk, AVD manager, or project target.
         
         // device chooser response object.
         final DeviceChooserResponse response = new DeviceChooserResponse();
@@ -644,81 +644,81 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
          * - Manually Mode
          *       Always display a UI that lets a user see the current running emulators/devices.
          *       The UI must show which devices are compatibles, and allow launching new emulators
-         *       with compatible (and not yet running) VM.
+         *       with compatible (and not yet running) AVD.
          * - Automatic Way
-         *     * Preferred VM set.
-         *           If Preferred VM is not running: launch it.
-         *           Launch the application on the preferred VM.
-         *     * No preferred VM.
+         *     * Preferred AVD set.
+         *           If Preferred AVD is not running: launch it.
+         *           Launch the application on the preferred AVD.
+         *     * No preferred AVD.
          *           Count the number of compatible emulators/devices.
          *           If != 1, display a UI similar to manual mode.
-         *           If == 1, launch the application on this VM/device.
+         *           If == 1, launch the application on this AVD/device.
          */
         
         if (config.mTargetMode == AndroidLaunchConfiguration.AUTO_TARGET_MODE) {
             // if we are in automatic target mode, we need to find the current devices
             Device[] devices = AndroidDebugBridge.getBridge().getDevices();
             
-            // first check if we have a preferred VM name, and if it actually exists, and is valid
+            // first check if we have a preferred AVD name, and if it actually exists, and is valid
             // (ie able to run the project).
-            // We need to check this in case the VM was recreated with a different target that is
+            // We need to check this in case the AVD was recreated with a different target that is
             // not compatible.
-            VmInfo preferredVm = null;
-            if (config.mVmName != null) {
-                preferredVm = vmManager.getVm(config.mVmName);
-                if (projectTarget.isCompatibleBaseFor(preferredVm.getTarget()) == false) {
-                    preferredVm = null;
+            AvdInfo preferredAvd = null;
+            if (config.mAvdName != null) {
+                preferredAvd = avdManager.getAvd(config.mAvdName);
+                if (projectTarget.isCompatibleBaseFor(preferredAvd.getTarget()) == false) {
+                    preferredAvd = null;
 
                     AdtPlugin.printErrorToConsole(project, String.format(
-                            "Preferred VM '%1$s' is not compatible with the project target '%2$s'. Looking for a compatible VM...",
-                            config.mVmName, projectTarget.getName()));
+                            "Preferred AVD '%1$s' is not compatible with the project target '%2$s'. Looking for a compatible AVD...",
+                            config.mAvdName, projectTarget.getName()));
                 }
             }
                 
-            if (preferredVm != null) {
+            if (preferredAvd != null) {
                 // look for a matching device
                 for (Device d : devices) {
-                    String deviceVm = d.getVmName();
-                    if (deviceVm != null && deviceVm.equals(config.mVmName)) {
+                    String deviceAvd = d.getAvdName();
+                    if (deviceAvd != null && deviceAvd.equals(config.mAvdName)) {
                         response.mustContinue = true;
                         response.mustLaunchEmulator = false;
                         response.deviceToUse = d;
 
                         AdtPlugin.printToConsole(project, String.format(
-                                "Automatic Target Mode: Preferred VM '%1$s' is available on emulator '%2$s'",
-                                config.mVmName, d));
+                                "Automatic Target Mode: Preferred AVD '%1$s' is available on emulator '%2$s'",
+                                config.mAvdName, d));
 
                         continueLaunch(response, project, launch, launchInfo, config);
                         return;
                     }
                 }
                 
-                // at this point we have a valid preferred VM that is not running.
+                // at this point we have a valid preferred AVD that is not running.
                 // We need to start it.
                 response.mustContinue = true;
                 response.mustLaunchEmulator = true;
-                response.vmToLaunch = preferredVm;
+                response.avdToLaunch = preferredAvd;
 
                 AdtPlugin.printToConsole(project, String.format(
-                        "Automatic Target Mode: Preferred VM '%1$s' is not available. Launching new emulator.",
-                        config.mVmName));
+                        "Automatic Target Mode: Preferred AVD '%1$s' is not available. Launching new emulator.",
+                        config.mAvdName));
 
                 continueLaunch(response, project, launch, launchInfo, config);
                 return;
             }
 
-            // no (valid) preferred VM? look for one.
-            HashMap<Device, VmInfo> compatibleRunningVms = new HashMap<Device, VmInfo>();
+            // no (valid) preferred AVD? look for one.
+            HashMap<Device, AvdInfo> compatibleRunningAvds = new HashMap<Device, AvdInfo>();
             boolean hasDevice = false; // if there's 1+ device running, we may force manual mode,
                                        // as we cannot always detect proper compatibility with
                                        // devices. This is the case if the project target is not
                                        // a standard platform
             for (Device d : devices) {
-                String deviceVm = d.getVmName();
-                if (deviceVm != null) { // physical devices return null.
-                    VmInfo info = vmManager.getVm(deviceVm);
+                String deviceAvd = d.getAvdName();
+                if (deviceAvd != null) { // physical devices return null.
+                    AvdInfo info = avdManager.getAvd(deviceAvd);
                     if (info != null && projectTarget.isCompatibleBaseFor(info.getTarget())) {
-                        compatibleRunningVms.put(d, info);
+                        compatibleRunningAvds.put(d, info);
                     }
                 } else {
                     if (projectTarget.isPlatform()) { // means this can run on any device as long
@@ -728,7 +728,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
                             int apiNumber = Integer.parseInt(apiString);
                             if (apiNumber >= projectTarget.getApiVersionNumber()) {
                                 // device is compatible with project
-                                compatibleRunningVms.put(d, null);
+                                compatibleRunningAvds.put(d, null);
                                 continue;
                             }
                         } catch (NumberFormatException e) {
@@ -741,54 +741,54 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
             
             // depending on the number of devices, we'll simulate an automatic choice
             // from the device chooser or simply show up the device chooser.
-            if (hasDevice == false && compatibleRunningVms.size() == 0) {
+            if (hasDevice == false && compatibleRunningAvds.size() == 0) {
                 // if zero emulators/devices, we launch an emulator.
-                // We need to figure out which VM first.
+                // We need to figure out which AVD first.
                 
-                // we are going to take the closest VM. ie a compatible VM that has the API level
+                // we are going to take the closest AVD. ie a compatible AVD that has the API level
                 // closest to the project target.
-                VmInfo[] vms = vmManager.getVms();
-                VmInfo defaultVm = null;
-                for (VmInfo vm : vms) {
-                    if (projectTarget.isCompatibleBaseFor(vm.getTarget())) {
-                        if (defaultVm == null ||
-                                vm.getTarget().getApiVersionNumber() <
-                                    defaultVm.getTarget().getApiVersionNumber()) {
-                            defaultVm = vm;
+                AvdInfo[] avds = avdManager.getAvds();
+                AvdInfo defaultAvd = null;
+                for (AvdInfo avd : avds) {
+                    if (projectTarget.isCompatibleBaseFor(avd.getTarget())) {
+                        if (defaultAvd == null ||
+                                avd.getTarget().getApiVersionNumber() <
+                                    defaultAvd.getTarget().getApiVersionNumber()) {
+                            defaultAvd = avd;
                         }
                     }
                 }
 
-                if (defaultVm != null) {
+                if (defaultAvd != null) {
                     response.mustContinue = true;
                     response.mustLaunchEmulator = true;
-                    response.vmToLaunch = defaultVm;
+                    response.avdToLaunch = defaultAvd;
 
                     AdtPlugin.printToConsole(project, String.format(
-                            "Automatic Target Mode: launching new emulator with compatible VM '%1$s'",
-                            defaultVm.getName()));
+                            "Automatic Target Mode: launching new emulator with compatible AVD '%1$s'",
+                            defaultAvd.getName()));
 
                     continueLaunch(response, project, launch, launchInfo, config);
                     return;
                 } else {
-                    // FIXME: ask the user if he wants to create a VM.
-                    // we found no compatible VM.
+                    // FIXME: ask the user if he wants to create a AVD.
+                    // we found no compatible AVD.
                     AdtPlugin.printErrorToConsole(project, String.format(
-                            "Failed to find a VM compatible with target '%1$s'. Launch aborted.",
+                            "Failed to find a AVD compatible with target '%1$s'. Launch aborted.",
                             projectTarget.getName()));
                     launch.stopLaunch();
                     return;
                 }
-            } else if (hasDevice == false && compatibleRunningVms.size() == 1) {
-                Entry<Device, VmInfo> e = compatibleRunningVms.entrySet().iterator().next();
+            } else if (hasDevice == false && compatibleRunningAvds.size() == 1) {
+                Entry<Device, AvdInfo> e = compatibleRunningAvds.entrySet().iterator().next();
                 response.mustContinue = true;
                 response.mustLaunchEmulator = false;
                 response.deviceToUse = e.getKey();
 
-                // get the VmInfo, if null, the device is a physical device.
-                VmInfo vmInfo = e.getValue();
-                if (vmInfo != null) {
-                    message = String.format("Automatic Target Mode: using existing emulator '%1$s' running compatible VM '%2$s'",
+                // get the AvdInfo, if null, the device is a physical device.
+                AvdInfo avdInfo = e.getValue();
+                if (avdInfo != null) {
+                    message = String.format("Automatic Target Mode: using existing emulator '%1$s' running compatible AVD '%2$s'",
                             response.deviceToUse, e.getValue().getName());
                 } else {
                     message = String.format("Automatic Target Mode: using device '%1$s'",
@@ -801,7 +801,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
             }
 
             // if more than one device, we'll bring up the DeviceChooser dialog below.
-            if (compatibleRunningVms.size() >= 2) {
+            if (compatibleRunningAvds.size() >= 2) {
                 message = "Automatic Target Mode: Several compatible targets. Please select a target device."; 
             } else if (hasDevice) {
                 message = "Automatic Target Mode: Unable to detect device compatibility. Please select a target device."; 
@@ -849,7 +849,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
                     synchronized (sListLock) {
                         mWaitingForEmulatorLaunches.add(launchInfo);
                         AdtPlugin.printToConsole(project, "Launching a new emulator.");
-                        boolean status = launchEmulator(config, response.vmToLaunch);
+                        boolean status = launchEmulator(config, response.avdToLaunch);
             
                         if (status == false) {
                             // launching the emulator failed!
@@ -1323,7 +1323,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
         }
     }
 
-    private boolean launchEmulator(AndroidLaunchConfiguration config, VmInfo vmToLaunch) {
+    private boolean launchEmulator(AndroidLaunchConfiguration config, AvdInfo avdToLaunch) {
 
         // split the custom command line in segments
         ArrayList<String> customArgs = new ArrayList<String>();
@@ -1353,8 +1353,8 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
         ArrayList<String> list = new ArrayList<String>();
 
         list.add(AdtPlugin.getOsAbsoluteEmulator());
-        list.add(FLAG_VM);
-        list.add(vmToLaunch.getName());
+        list.add(FLAG_AVD);
+        list.add(avdToLaunch.getName());
         
         if (config.mNetworkSpeed != null) {
             list.add(FLAG_NETSPEED);
index 19ec9a7..05bc171 100644 (file)
@@ -31,7 +31,7 @@ import com.android.ide.eclipse.adt.debug.launching.AndroidLaunchController.Delay
 import com.android.ide.eclipse.adt.sdk.Sdk;
 import com.android.ide.eclipse.ddms.DdmsPlugin;
 import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.vm.VmManager.VmInfo;
+import com.android.sdklib.avd.AvdManager.AvdInfo;
 
 import org.eclipse.core.resources.IProject;
 import org.eclipse.jface.preference.IPreferenceStore;
@@ -70,10 +70,10 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener
     private final static int ICON_WIDTH = 16;
 
     private final static String PREFS_COL_SERIAL = "deviceChooser.serial"; //$NON-NLS-1$
-    private final static String PREFS_COL_STATE = "deviceChooser.state"; //$NON-NLS-1$
-    private final static String PREFS_COL_VM = "deviceChooser.vm"; //$NON-NLS-1$
+    private final static String PREFS_COL_STATE  = "deviceChooser.state"; //$NON-NLS-1$
+    private final static String PREFS_COL_AVD     = "deviceChooser.avd"; //$NON-NLS-1$
     private final static String PREFS_COL_TARGET = "deviceChooser.target"; //$NON-NLS-1$
-    private final static String PREFS_COL_DEBUG = "deviceChooser.debug"; //$NON-NLS-1$
+    private final static String PREFS_COL_DEBUG  = "deviceChooser.debug"; //$NON-NLS-1$
 
     private Table mDeviceTable;
     private TableViewer mViewer;
@@ -149,8 +149,8 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener
                                 return mNoMatchImage;
                             }
                         } else {
-                            // get the VmInfo
-                            VmInfo info = mSdk.getVmManager().getVm(device.getVmName());
+                            // get the AvdInfo
+                            AvdInfo info = mSdk.getAvdManager().getAvd(device.getAvdName());
                             if (info == null) {
                                 return mWarningImage;
                             }
@@ -171,13 +171,13 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener
                         return device.getSerialNumber();
                     case 1:
                         if (device.isEmulator()) {
-                            return device.getVmName();
+                            return device.getAvdName();
                         } else {
-                            return "N/A"; // devices don't have VM names.
+                            return "N/A"; // devices don't have AVD names.
                         }
                     case 2:
                         if (device.isEmulator()) {
-                            VmInfo info = mSdk.getVmManager().getVm(device.getVmName());
+                            AvdInfo info = mSdk.getAvdManager().getAvd(device.getAvdName());
                             if (info == null) {
                                 return "?";
                             }
@@ -221,7 +221,7 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener
     public static class DeviceChooserResponse {
         public boolean mustContinue;
         public boolean mustLaunchEmulator;
-        public VmInfo vmToLaunch;
+        public AvdInfo avdToLaunch;
         public Device deviceToUse;
     }
     
@@ -314,9 +314,9 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener
                 SWT.LEFT, "AAA+AAAAAAAAAAAAAAAAAAA", //$NON-NLS-1$
                 PREFS_COL_SERIAL, store);
 
-        TableHelper.createTableColumn(mDeviceTable, "VM Name",
+        TableHelper.createTableColumn(mDeviceTable, "AVD Name",
                 SWT.LEFT, "engineering", //$NON-NLS-1$
-                PREFS_COL_VM, store);
+                PREFS_COL_AVD, store);
 
         TableHelper.createTableColumn(mDeviceTable, "Target",
                 SWT.LEFT, "AAA+Android 9.9.9", //$NON-NLS-1$
index 68deec3..bbd320b 100644 (file)
@@ -80,7 +80,7 @@ public class LaunchConfigDelegate extends LaunchConfigurationDelegate {
      */
     public static final String ATTR_ACTIVITY = AdtPlugin.PLUGIN_ID + ".activity"; //$NON-NLS-1$
 
-    public static final String ATTR_VM_NAME = AdtPlugin.PLUGIN_ID + ".vm"; //$NON-NLS-1$
+    public static final String ATTR_AVD_NAME = AdtPlugin.PLUGIN_ID + ".avd"; //$NON-NLS-1$
     
     public static final String ATTR_SPEED = AdtPlugin.PLUGIN_ID + ".speed"; //$NON-NLS-1$
 
index f4f5281..a581e5c 100644 (file)
@@ -22,9 +22,9 @@ import com.android.ide.eclipse.adt.sdk.Sdk;
 import com.android.ide.eclipse.common.project.BaseProjectHelper;
 import com.android.ide.eclipse.ddms.DdmsPlugin;
 import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.vm.VmManager;
-import com.android.sdklib.vm.VmManager.VmInfo;
-import com.android.sdkuilib.VmSelector;
+import com.android.sdklib.avd.AvdManager;
+import com.android.sdklib.avd.AvdManager.AvdInfo;
+import com.android.sdkuilib.AvdSelector;
 
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.runtime.CoreException;
@@ -75,7 +75,7 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
     private Button mAutoTargetButton;
     private Button mManualTargetButton;
 
-    private VmSelector mPreferredVmSelector;
+    private AvdSelector mPreferredAvdSelector;
 
     private Combo mSpeedCombo;
 
@@ -163,11 +163,11 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
             }
         });
 
-        new Label(targetModeGroup, SWT.NONE).setText("Preferred VM");
-        VmInfo[] vms = new VmInfo[0];
-        mPreferredVmSelector = new VmSelector(targetModeGroup, vms,
+        new Label(targetModeGroup, SWT.NONE).setText("Preferred Android Virtual Device");
+        AvdInfo[] avds = new AvdInfo[0];
+        mPreferredAvdSelector = new AvdSelector(targetModeGroup, avds,
                 false /*allowMultipleSelection*/);
-        mPreferredVmSelector.setSelectionListener(new SelectionAdapter() {
+        mPreferredAvdSelector.setSelectionListener(new SelectionAdapter() {
             @Override
             public void widgetSelected(SelectionEvent e) {
                 updateLaunchConfigurationDialog();
@@ -277,7 +277,7 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
      * @see org.eclipse.debug.ui.ILaunchConfigurationTab#initializeFrom(org.eclipse.debug.core.ILaunchConfiguration)
      */
     public void initializeFrom(ILaunchConfiguration configuration) {
-        VmManager vmManager = Sdk.getCurrent().getVmManager();
+        AvdManager avdManager = Sdk.getCurrent().getAvdManager();
 
         boolean value = LaunchConfigDelegate.DEFAULT_TARGET_MODE; // true == automatic
         try {
@@ -311,34 +311,34 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
             }
         }
 
-        // update the VM list
-        VmInfo[] vms = null;
-        if (vmManager != null) {
-            vms = vmManager.getVms();
+        // update the AVD list
+        AvdInfo[] avds = null;
+        if (avdManager != null) {
+            avds = avdManager.getAvds();
         }
 
         IAndroidTarget projectTarget = null;
         if (project != null) {
             projectTarget = Sdk.getCurrent().getTarget(project);
         } else {
-            vms = null; // no project? we don't want to display any "compatible" VMs.
+            avds = null; // no project? we don't want to display any "compatible" AVDs.
         }
         
-        mPreferredVmSelector.setVms(vms, projectTarget);
+        mPreferredAvdSelector.setAvds(avds, projectTarget);
 
         stringValue = "";
         try {
-            stringValue = configuration.getAttribute(LaunchConfigDelegate.ATTR_VM_NAME,
+            stringValue = configuration.getAttribute(LaunchConfigDelegate.ATTR_AVD_NAME,
                     stringValue);
         } catch (CoreException e) {
             // let's not do anything here, we'll use the default value
         }
 
-        if (stringValue != null && stringValue.length() > 0 && vmManager != null) {
-            VmInfo targetVm = vmManager.getVm(stringValue);
-            mPreferredVmSelector.setSelection(targetVm);
+        if (stringValue != null && stringValue.length() > 0 && avdManager != null) {
+            AvdInfo targetAvd = avdManager.getAvd(stringValue);
+            mPreferredAvdSelector.setSelection(targetAvd);
         } else {
-            mPreferredVmSelector.setSelection(null);
+            mPreferredAvdSelector.setSelection(null);
         }
 
         value = LaunchConfigDelegate.DEFAULT_WIPE_DATA;
@@ -404,11 +404,11 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
     public void performApply(ILaunchConfigurationWorkingCopy configuration) {
         configuration.setAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE,
                 mAutoTargetButton.getSelection());
-        VmInfo vm = mPreferredVmSelector.getFirstSelected();
-        if (vm != null) {
-            configuration.setAttribute(LaunchConfigDelegate.ATTR_VM_NAME, vm.getName());
+        AvdInfo avd = mPreferredAvdSelector.getFirstSelected();
+        if (avd != null) {
+            configuration.setAttribute(LaunchConfigDelegate.ATTR_AVD_NAME, avd.getName());
         } else {
-            configuration.setAttribute(LaunchConfigDelegate.ATTR_VM_NAME, (String)null);
+            configuration.setAttribute(LaunchConfigDelegate.ATTR_AVD_NAME, (String)null);
         }
         configuration.setAttribute(LaunchConfigDelegate.ATTR_SPEED,
                 mSpeedCombo.getSelectionIndex());
index 8678923..cbeddd7 100644 (file)
@@ -361,7 +361,7 @@ public final class ProjectHelper {
     }
 
     /**
-     * Returns a {@link IProject} by its running application name, as it returned by the VM.
+     * Returns a {@link IProject} by its running application name, as it returned by the AVD.
      * <p/>
      * <var>applicationName</var> will in most case be the package declared in the manifest, but
      * can, in some cases, be a custom process name declared in the manifest, in the
index 172b4ae..01b722f 100644 (file)
@@ -23,9 +23,9 @@ import com.android.sdklib.IAndroidTarget;
 import com.android.sdklib.ISdkLog;
 import com.android.sdklib.SdkConstants;
 import com.android.sdklib.SdkManager;
+import com.android.sdklib.avd.AvdManager;
 import com.android.sdklib.project.ProjectProperties;
 import com.android.sdklib.project.ProjectProperties.PropertyType;
-import com.android.sdklib.vm.VmManager;
 
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.runtime.IStatus;
@@ -52,7 +52,7 @@ public class Sdk {
     private static Sdk sCurrentSdk = null;
 
     private final SdkManager mManager;
-    private final VmManager mVmManager;
+    private final AvdManager mAvdManager;
 
     private final HashMap<IProject, IAndroidTarget> mProjectMap =
             new HashMap<IProject, IAndroidTarget>();
@@ -95,13 +95,13 @@ public class Sdk {
         // get an SdkManager object for the location
         SdkManager manager = SdkManager.createManager(sdkLocation, log);
         if (manager != null) {
-            VmManager vmManager = null;
+            AvdManager avdManager = null;
             try {
-                vmManager = new VmManager(manager, log);
+                avdManager = new AvdManager(manager, log);
             } catch (AndroidLocationException e) {
-                log.error(e, "Error parsing the VMs");
+                log.error(e, "Error parsing the AVDs");
             }
-            sCurrentSdk = new Sdk(manager, vmManager);
+            sCurrentSdk = new Sdk(manager, avdManager);
             return sCurrentSdk;
         } else {
             StringBuilder sb = new StringBuilder("Error Loading the SDK:\n");
@@ -255,16 +255,16 @@ public class Sdk {
     }
     
     /**
-     * Returns the {@link VmManager}. If the VmManager failed to parse the VM folder, this could
+     * Returns the {@link AvdManager}. If the AvdManager failed to parse the AVD folder, this could
      * be <code>null</code>.
      */
-    public VmManager getVmManager() {
-        return mVmManager;
+    public AvdManager getAvdManager() {
+        return mAvdManager;
     }
     
-    private Sdk(SdkManager manager, VmManager vmManager) {
+    private Sdk(SdkManager manager, AvdManager avdManager) {
         mManager = manager;
-        mVmManager = vmManager;
+        mAvdManager = avdManager;
         
         // pre-compute some paths
         mDocBaseUrl = getDocumentationBaseUrl(mManager.getLocation() +
index 0f388f4..d5ee2ca 100644 (file)
@@ -187,10 +187,10 @@ public final class CustomViewDescriptorService {
     /**
      * Computes (if needed) and returns the {@link ElementDescriptor} for the specified type.
      * 
-     * @param type
+     * @param type 
      * @param project 
      * @param typeHierarchy
-     * @return A ViewElementDescriptor
+     * @return A ViewElementDescriptor or null if type or typeHierarchy is null.
      */
     private ViewElementDescriptor getDescriptor(IType type, IProject project,
             ITypeHierarchy typeHierarchy) {
@@ -198,12 +198,17 @@ public final class CustomViewDescriptorService {
         List<ElementDescriptor> builtInList = null;
 
         Sdk currentSdk = Sdk.getCurrent();
-        IAndroidTarget target = currentSdk.getTarget(project);
+        IAndroidTarget target = currentSdk == null ? null : currentSdk.getTarget(project);
         if (target != null) {
             AndroidTargetData data = currentSdk.getTargetData(target);
             builtInList = data.getLayoutDescriptors().getViewDescriptors();
         }
 
+        // give up if there's no type
+        if (type == null) {
+            return null;
+        }
+
         String canonicalName = type.getFullyQualifiedName();
         
         if (builtInList != null) {
@@ -218,6 +223,11 @@ public final class CustomViewDescriptorService {
         }
         
         // it's not a built-in class? Lets look if the superclass is built-in
+        // give up if there's no type
+        if (typeHierarchy == null) {
+            return null;
+        }
+
         IType parentType = typeHierarchy.getSuperclass(type);
         if (parentType != null) {
             ViewElementDescriptor parentDescriptor = getDescriptor(parentType, project,
index 5908574..4a9fbb1 100644 (file)
@@ -79,7 +79,7 @@ public abstract class UiAbstractTextAttributeNode extends UiAttributeNode
     public void updateValue(Node xml_attribute_node) {
         mCurrentValue = DEFAULT_VALUE;
         if (xml_attribute_node != null) {
-            mCurrentValue = xml_attribute_node.getNodeValue().trim();
+            mCurrentValue = xml_attribute_node.getNodeValue();
         }
 
         if (isValid() && !getTextWidgetValue().equals(mCurrentValue)) {
@@ -101,7 +101,7 @@ public abstract class UiAbstractTextAttributeNode extends UiAttributeNode
     public void commit() {
         UiElementNode parent = getUiParent();
         if (parent != null && isValid() && isDirty()) {
-            String value = getTextWidgetValue().trim();
+            String value = getTextWidgetValue();
             if (parent.commitAttributeToXml(this, value)) {
                 mCurrentValue = value;
                 setDirty(false);
index 5a89d01..8c52d81 100644 (file)
@@ -22,7 +22,6 @@ import com.android.ide.eclipse.mock.JavaProjectMock;
 import org.eclipse.core.runtime.Path;
 import org.eclipse.jdt.core.IClasspathEntry;
 import org.eclipse.jdt.core.JavaModelException;
-import org.eclipse.jdt.launching.JavaRuntime;
 
 import junit.framework.TestCase;
 
index 872938b..8af7e02 100644 (file)
@@ -28,7 +28,7 @@ import java.util.HashMap;
 import junit.framework.TestCase;
 
 /**
- * Unit Test for {@link FrameworkClassLoader}.
+ * Unit Test for {@link AndroidJarLoader}.
  * 
  * Uses the classes jar.example.Class1/Class2 stored in tests/data/jar_example.jar.
  */
@@ -36,7 +36,7 @@ public class AndroidJarLoaderTest extends TestCase {
 
     private AndroidJarLoader mFrameworkClassLoader;
 
-    /** Creates an instance of {@link FrameworkClassLoader} on our test data JAR */ 
+    /** Creates an instance of {@link AndroidJarLoader} on our test data JAR */ 
     @Override
     public void setUp() throws Exception {
         String jarfilePath = AdtTestData.getInstance().getTestFilePath("jar_example.jar");  //$NON-NLS-1$
index b66fcd6..cedf4d4 100644 (file)
@@ -35,7 +35,7 @@ import junit.framework.TestCase;
  * Test the inner private methods of PlatformDataParser.
  * 
  * Convention: method names that start with an underscore are actually local wrappers
- * that call private methods from {@link FrameworkResourceParser} using reflection.
+ * that call private methods from {@link AndroidTargetParser} using reflection.
  * This is inspired by the Python coding rule which mandates underscores prefixes for
  * "private" methods.
  */
@@ -131,6 +131,7 @@ public class LayoutParamsParserTest extends TestCase {
     //---- access to private methods
     
     /** Calls the private constructor of the parser */
+    @SuppressWarnings("unused")
     private AndroidTargetParser _Constructor(String osJarPath) throws Exception {
         Constructor<AndroidTargetParser> constructor =
             AndroidTargetParser.class.getDeclaredConstructor(String.class);
@@ -139,6 +140,7 @@ public class LayoutParamsParserTest extends TestCase {
     }
     
     /** calls the private getLayoutClasses() of the parser */
+    @SuppressWarnings("unused")
     private void _getLayoutClasses() throws Exception {
         Method method = AndroidTargetParser.class.getDeclaredMethod("getLayoutClasses");  //$NON-NLS-1$
         method.setAccessible(true);
@@ -146,6 +148,7 @@ public class LayoutParamsParserTest extends TestCase {
     }
     
     /** calls the private addGroup() of the parser */
+    @SuppressWarnings("unused")
     private ViewClassInfo _addGroup(Class<?> groupClass) throws Exception {
         Method method = LayoutParamsParser.class.getDeclaredMethod("addGroup",  //$NON-NLS-1$
                 IClassDescriptor.class);
@@ -154,6 +157,7 @@ public class LayoutParamsParserTest extends TestCase {
     }
 
     /** calls the private addLayoutParams() of the parser */
+    @SuppressWarnings("unused")
     private LayoutParamsInfo _addLayoutParams(Class<?> groupClass) throws Exception {
         Method method = LayoutParamsParser.class.getDeclaredMethod("addLayoutParams",   //$NON-NLS-1$
                 IClassDescriptor.class);
index 963e2f1..748139a 100755 (executable)
@@ -25,9 +25,24 @@ fi
 
 for app in $apps
 do
+    echo '-----------------------------------------------------------'
     if [ -d $app/res ]
     then
         appname=$(basename $app)
+        resources=
+        for res in $(echo $app/res/*)
+        do
+            resources="$resources $(echo $res | grep -v '\-mcc\|[a-z]*-[a-z][a-z]$\|[a-z]*-[a-z][a-z]-.*')"
+        done
+        sources=$app/src
+        if [ -d $app/tests ]
+        then
+            sources="$sources $app/tests"
+        fi
+        if [ -d $app/samples ]
+        then
+            sources="$sources $app/samples"
+        fi
 
         # find the R.java file that contains all the generated resource identifiers
         rDotJava=$(find out/target/common/obj/APPS/${appname}_intermediates/ -name R.java)
@@ -40,7 +55,7 @@ do
             # refer to such constants from java by using an underscore instead of a period, we also
             # replace all underscores with a pattern that will match periods and underscores.
             p=$(echo $i | sed 's/_/[\\._]/g')
-            echo $i $(grep -Rw R\\..*\\.$i\\\|@style/$p\\\|@drawable/$p\\\|@anim/$p\\\|@color/$p\\\|@xml/$p\\\|@layout/$p\\\|@menu/$p\\\|@+id/$p\\\|@array/$p\\\|@string/$p $app | wc -l)
+            echo $i $(grep -Rw R\\..*\\.$i\\\|@style/$p\\\|@drawable/$p\\\|@anim/$p\\\|@color/$p\\\|@xml/$p\\\|@layout/$p\\\|@menu/$p\\\|@+id/$p\\\|@array/$p\\\|@string/$p\\\|@dimen/$p $resources $sources $app/AndroidManifest.xml | wc -l)
         done | grep " 0$" | {
             # this block gets as its input a list of constants which no references were found, one per line
             if [ "$showall" == "yes" ]
index 9978504..2694f3a 100755 (executable)
@@ -99,8 +99,8 @@ function showUsage() {
 #
 # In order to define the most common cases simply, "#" can be used for some of
 # the fields, with the following default values:
-#   <test-package> = "#": test class is fully qualified with package
 #   <build-path> = "#":  skip build/sync step
+#   <test-package> = "#": test class is fully qualified with package
 #   <test-class> = "#":  omit "-e class" section
 #   <testrunner-package> = "#":   use same value as test-package
 #   <testrunner-component> = "#":  use "android.test.InstrumentationTestRunner"
@@ -117,7 +117,8 @@ knownTests=(
   "smoke      frameworks/base/tests/SmokeTest     com.android.smoketest # com.android.smoketest.tests #"
   "core       frameworks/base/tests/CoreTests     # android.core.CoreTests android.core #"
   "libcore    frameworks/base/tests/CoreTests     # android.core.JavaTests android.core #"
-  "apidemos   samples/ApiDemos                    com.example.android.apis # com.example.android.apis.tests #"
+  "apidemos   development/samples/ApiDemos        com.example.android.apis # com.example.android.apis.tests #"
+  "launchperf development/apps/launchperf         com.android.launchperf # # .SimpleActivityLaunchPerformance"
 
   # targeted framework tests
   "heap       frameworks/base/tests/AndroidTests  com.android.unit_tests HeapTest # #"
@@ -130,7 +131,7 @@ knownTests=(
   "browser    packages/apps/Browser            com.android.browser # # .BrowserTestRunner"
   "browserfunc packages/apps/Browser           com.android.browser # # .BrowserFunctionalTestRunner"
   "calendar   packages/apps/Calendar/tests     com.android.calendar.tests # # #"
-  "calprov    content/providers/calendar   com.android.providers.calendar.tests # # #"
+  "calprov    packages/providers/CalendarProvider   com.android.providers.calendar # com.android.providers.calendar.tests #"
   "camera     tests/Camera            com.android.cameratests # # CameraInstrumentationTestRunner"
   "contactsprov packages/providers/GoogleContactsProvider/tests com.android.providers.contacts # com.android.providers.contactstests #"
   "email      packages/apps/Email              com.android.email # com.android.email.tests #"
diff --git a/tools/scripts/app_engine_server/LICENSE b/tools/scripts/app_engine_server/LICENSE
new file mode 100644 (file)
index 0000000..d645695
--- /dev/null
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.
diff --git a/tools/scripts/app_engine_server/app.yaml b/tools/scripts/app_engine_server/app.yaml
new file mode 100755 (executable)
index 0000000..1fb50c7
--- /dev/null
@@ -0,0 +1,16 @@
+application: androidappdocs-staging
+version: 1
+runtime: python
+api_version: 1
+
+handlers:
+- url: /gae_shell/static
+  static_dir: gae_shell/static
+  expiration: 1d
+
+- url: /gae_shell/.*
+  script: /gae_shell/shell.py
+  login: admin
+
+- url: .*
+  script: main.py
diff --git a/tools/scripts/app_engine_server/gae_shell/README b/tools/scripts/app_engine_server/gae_shell/README
new file mode 100644 (file)
index 0000000..5b0089f
--- /dev/null
@@ -0,0 +1,17 @@
+An interactive, stateful AJAX shell that runs Python code on the server.
+
+Part of http://code.google.com/p/google-app-engine-samples/.
+
+May be run as a standalone app or in an existing app as an admin-only handler.
+Can be used for system administration tasks, as an interactive way to try out
+APIs, or as a debugging aid during development.
+
+The logging, os, sys, db, and users modules are imported automatically.
+
+Interpreter state is stored in the datastore so that variables, function
+definitions, and other values in the global and local namespaces can be used
+across commands.
+
+To use the shell in your app, copy shell.py, static/*, and templates/* into
+your app's source directory. Then, copy the URL handlers from app.yaml into
+your app.yaml.
diff --git a/tools/scripts/app_engine_server/gae_shell/__init__.py b/tools/scripts/app_engine_server/gae_shell/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tools/scripts/app_engine_server/gae_shell/__init__.pyc b/tools/scripts/app_engine_server/gae_shell/__init__.pyc
new file mode 100644 (file)
index 0000000..84951e9
Binary files /dev/null and b/tools/scripts/app_engine_server/gae_shell/__init__.pyc differ
diff --git a/tools/scripts/app_engine_server/gae_shell/shell.py b/tools/scripts/app_engine_server/gae_shell/shell.py
new file mode 100755 (executable)
index 0000000..df2fb17
--- /dev/null
@@ -0,0 +1,308 @@
+#!/usr/bin/python
+#
+# Copyright 2007 Google Inc.
+#
+# 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.
+
+"""
+An interactive, stateful AJAX shell that runs Python code on the server.
+
+Part of http://code.google.com/p/google-app-engine-samples/.
+
+May be run as a standalone app or in an existing app as an admin-only handler.
+Can be used for system administration tasks, as an interactive way to try out
+APIs, or as a debugging aid during development.
+
+The logging, os, sys, db, and users modules are imported automatically.
+
+Interpreter state is stored in the datastore so that variables, function
+definitions, and other values in the global and local namespaces can be used
+across commands.
+
+To use the shell in your app, copy shell.py, static/*, and templates/* into
+your app's source directory. Then, copy the URL handlers from app.yaml into
+your app.yaml.
+
+TODO: unit tests!
+"""
+
+import logging
+import new
+import os
+import pickle
+import sys
+import traceback
+import types
+import wsgiref.handlers
+
+from google.appengine.api import users
+from google.appengine.ext import db
+from google.appengine.ext import webapp
+from google.appengine.ext.webapp import template
+
+
+# Set to True if stack traces should be shown in the browser, etc.
+_DEBUG = True
+
+# The entity kind for shell sessions. Feel free to rename to suit your app.
+_SESSION_KIND = '_Shell_Session'
+
+# Types that can't be pickled.
+UNPICKLABLE_TYPES = (
+  types.ModuleType,
+  types.TypeType,
+  types.ClassType,
+  types.FunctionType,
+  )
+
+# Unpicklable statements to seed new sessions with.
+INITIAL_UNPICKLABLES = [
+  'import logging',
+  'import os',
+  'import sys',
+  'from google.appengine.ext import db',
+  'from google.appengine.api import users',
+  ]
+
+
+class Session(db.Model):
+  """A shell session. Stores the session's globals.
+
+  Each session globals is stored in one of two places:
+
+  If the global is picklable, it's stored in the parallel globals and
+  global_names list properties. (They're parallel lists to work around the
+  unfortunate fact that the datastore can't store dictionaries natively.)
+
+  If the global is not picklable (e.g. modules, classes, and functions), or if
+  it was created by the same statement that created an unpicklable global,
+  it's not stored directly. Instead, the statement is stored in the
+  unpicklables list property. On each request, before executing the current
+  statement, the unpicklable statements are evaluated to recreate the
+  unpicklable globals.
+
+  The unpicklable_names property stores all of the names of globals that were
+  added by unpicklable statements. When we pickle and store the globals after
+  executing a statement, we skip the ones in unpicklable_names.
+
+  Using Text instead of string is an optimization. We don't query on any of
+  these properties, so they don't need to be indexed.
+  """
+  global_names = db.ListProperty(db.Text)
+  globals = db.ListProperty(db.Blob)
+  unpicklable_names = db.ListProperty(db.Text)
+  unpicklables = db.ListProperty(db.Text)
+
+  def set_global(self, name, value):
+    """Adds a global, or updates it if it already exists.
+
+    Also removes the global from the list of unpicklable names.
+
+    Args:
+      name: the name of the global to remove
+      value: any picklable value
+    """
+    blob = db.Blob(pickle.dumps(value))
+
+    if name in self.global_names:
+      index = self.global_names.index(name)
+      self.globals[index] = blob
+    else:
+      self.global_names.append(db.Text(name))
+      self.globals.append(blob)
+
+    self.remove_unpicklable_name(name)
+
+  def remove_global(self, name):
+    """Removes a global, if it exists.
+
+    Args:
+      name: string, the name of the global to remove
+    """
+    if name in self.global_names:
+      index = self.global_names.index(name)
+      del self.global_names[index]
+      del self.globals[index]
+
+  def globals_dict(self):
+    """Returns a dictionary view of the globals.
+    """
+    return dict((name, pickle.loads(val))
+                for name, val in zip(self.global_names, self.globals))
+
+  def add_unpicklable(self, statement, names):
+    """Adds a statement and list of names to the unpicklables.
+
+    Also removes the names from the globals.
+
+    Args:
+      statement: string, the statement that created new unpicklable global(s).
+      names: list of strings; the names of the globals created by the statement.
+    """
+    self.unpicklables.append(db.Text(statement))
+
+    for name in names:
+      self.remove_global(name)
+      if name not in self.unpicklable_names:
+        self.unpicklable_names.append(db.Text(name))
+
+  def remove_unpicklable_name(self, name):
+    """Removes a name from the list of unpicklable names, if it exists.
+
+    Args:
+      name: string, the name of the unpicklable global to remove
+    """
+    if name in self.unpicklable_names:
+      self.unpicklable_names.remove(name)
+
+
+class FrontPageHandler(webapp.RequestHandler):
+  """Creates a new session and renders the shell.html template.
+  """
+
+  def get(self):
+    # set up the session. TODO: garbage collect old shell sessions
+    session_key = self.request.get('session')
+    if session_key:
+      session = Session.get(session_key)
+    else:
+      # create a new session
+      session = Session()
+      session.unpicklables = [db.Text(line) for line in INITIAL_UNPICKLABLES]
+      session_key = session.put()
+
+    template_file = os.path.join(os.path.dirname(__file__), 'templates',
+                                 'shell.html')
+    session_url = '/?session=%s' % session_key
+    vars = { 'server_software': os.environ['SERVER_SOFTWARE'],
+             'python_version': sys.version,
+             'session': str(session_key),
+             'user': users.get_current_user(),
+             'login_url': users.create_login_url(session_url),
+             'logout_url': users.create_logout_url(session_url),
+             }
+    rendered = webapp.template.render(template_file, vars, debug=_DEBUG)
+    self.response.out.write(rendered)
+
+
+class StatementHandler(webapp.RequestHandler):
+  """Evaluates a python statement in a given session and returns the result.
+  """
+
+  def get(self):
+    self.response.headers['Content-Type'] = 'text/plain'
+
+    # extract the statement to be run
+    statement = self.request.get('statement')
+    if not statement:
+      return
+
+    # the python compiler doesn't like network line endings
+    statement = statement.replace('\r\n', '\n')
+
+    # add a couple newlines at the end of the statement. this makes
+    # single-line expressions such as 'class Foo: pass' evaluate happily.
+    statement += '\n\n'
+
+    # log and compile the statement up front
+    try:
+      logging.info('Compiling and evaluating:\n%s' % statement)
+      compiled = compile(statement, '<string>', 'single')
+    except:
+      self.response.out.write(traceback.format_exc())
+      return
+
+    # create a dedicated module to be used as this statement's __main__
+    statement_module = new.module('__main__')
+
+    # use this request's __builtin__, since it changes on each request.
+    # this is needed for import statements, among other things.
+    import __builtin__
+    statement_module.__builtins__ = __builtin__
+
+    # load the session from the datastore
+    session = Session.get(self.request.get('session'))
+
+    # swap in our custom module for __main__. then unpickle the session
+    # globals, run the statement, and re-pickle the session globals, all
+    # inside it.
+    old_main = sys.modules.get('__main__')
+    try:
+      sys.modules['__main__'] = statement_module
+      statement_module.__name__ = '__main__'
+
+      # re-evaluate the unpicklables
+      for code in session.unpicklables:
+        exec code in statement_module.__dict__
+
+      # re-initialize the globals
+      for name, val in session.globals_dict().items():
+        try:
+          statement_module.__dict__[name] = val
+        except:
+          msg = 'Dropping %s since it could not be unpickled.\n' % name
+          self.response.out.write(msg)
+          logging.warning(msg + traceback.format_exc())
+          session.remove_global(name)
+
+      # run!
+      old_globals = dict(statement_module.__dict__)
+      try:
+        old_stdout = sys.stdout
+        old_stderr = sys.stderr
+        try:
+          sys.stdout = self.response.out
+          sys.stderr = self.response.out
+          exec compiled in statement_module.__dict__
+        finally:
+          sys.stdout = old_stdout
+          sys.stderr = old_stderr
+      except:
+        self.response.out.write(traceback.format_exc())
+        return
+
+      # extract the new globals that this statement added
+      new_globals = {}
+      for name, val in statement_module.__dict__.items():
+        if name not in old_globals or val != old_globals[name]:
+          new_globals[name] = val
+
+      if True in [isinstance(val, UNPICKLABLE_TYPES)
+                  for val in new_globals.values()]:
+        # this statement added an unpicklable global. store the statement and
+        # the names of all of the globals it added in the unpicklables.
+        session.add_unpicklable(statement, new_globals.keys())
+        logging.debug('Storing this statement as an unpicklable.')
+
+      else:
+        # this statement didn't add any unpicklables. pickle and store the
+        # new globals back into the datastore.
+        for name, val in new_globals.items():
+          if not name.startswith('__'):
+            session.set_global(name, val)
+
+    finally:
+      sys.modules['__main__'] = old_main
+
+    session.put()
+
+
+def main():
+  application = webapp.WSGIApplication(
+    [('/gae_shell/', FrontPageHandler),
+     ('/gae_shell/shell.do', StatementHandler)], debug=_DEBUG)
+  wsgiref.handlers.CGIHandler().run(application)
+
+
+if __name__ == '__main__':
+  main()
diff --git a/tools/scripts/app_engine_server/gae_shell/shell.py~ b/tools/scripts/app_engine_server/gae_shell/shell.py~
new file mode 100755 (executable)
index 0000000..dee9fdb
--- /dev/null
@@ -0,0 +1,308 @@
+#!/usr/bin/python
+#
+# Copyright 2007 Google Inc.
+#
+# 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.
+
+"""
+An interactive, stateful AJAX shell that runs Python code on the server.
+
+Part of http://code.google.com/p/google-app-engine-samples/.
+
+May be run as a standalone app or in an existing app as an admin-only handler.
+Can be used for system administration tasks, as an interactive way to try out
+APIs, or as a debugging aid during development.
+
+The logging, os, sys, db, and users modules are imported automatically.
+
+Interpreter state is stored in the datastore so that variables, function
+definitions, and other values in the global and local namespaces can be used
+across commands.
+
+To use the shell in your app, copy shell.py, static/*, and templates/* into
+your app's source directory. Then, copy the URL handlers from app.yaml into
+your app.yaml.
+
+TODO: unit tests!
+"""
+
+import logging
+import new
+import os
+import pickle
+import sys
+import traceback
+import types
+import wsgiref.handlers
+
+from google.appengine.api import users
+from google.appengine.ext import db
+from google.appengine.ext import webapp
+from google.appengine.ext.webapp import template
+
+
+# Set to True if stack traces should be shown in the browser, etc.
+_DEBUG = True
+
+# The entity kind for shell sessions. Feel free to rename to suit your app.
+_SESSION_KIND = '_Shell_Session'
+
+# Types that can't be pickled.
+UNPICKLABLE_TYPES = (
+  types.ModuleType,
+  types.TypeType,
+  types.ClassType,
+  types.FunctionType,
+  )
+
+# Unpicklable statements to seed new sessions with.
+INITIAL_UNPICKLABLES = [
+  'import logging',
+  'import os',
+  'import sys',
+  'from google.appengine.ext import db',
+  'from google.appengine.api import users',
+  ]
+
+
+class Session(db.Model):
+  """A shell session. Stores the session's globals.
+
+  Each session globals is stored in one of two places:
+
+  If the global is picklable, it's stored in the parallel globals and
+  global_names list properties. (They're parallel lists to work around the
+  unfortunate fact that the datastore can't store dictionaries natively.)
+
+  If the global is not picklable (e.g. modules, classes, and functions), or if
+  it was created by the same statement that created an unpicklable global,
+  it's not stored directly. Instead, the statement is stored in the
+  unpicklables list property. On each request, before executing the current
+  statement, the unpicklable statements are evaluated to recreate the
+  unpicklable globals.
+
+  The unpicklable_names property stores all of the names of globals that were
+  added by unpicklable statements. When we pickle and store the globals after
+  executing a statement, we skip the ones in unpicklable_names.
+
+  Using Text instead of string is an optimization. We don't query on any of
+  these properties, so they don't need to be indexed.
+  """
+  global_names = db.ListProperty(db.Text)
+  globals = db.ListProperty(db.Blob)
+  unpicklable_names = db.ListProperty(db.Text)
+  unpicklables = db.ListProperty(db.Text)
+
+  def set_global(self, name, value):
+    """Adds a global, or updates it if it already exists.
+
+    Also removes the global from the list of unpicklable names.
+
+    Args:
+      name: the name of the global to remove
+      value: any picklable value
+    """
+    blob = db.Blob(pickle.dumps(value))
+
+    if name in self.global_names:
+      index = self.global_names.index(name)
+      self.globals[index] = blob
+    else:
+      self.global_names.append(db.Text(name))
+      self.globals.append(blob)
+
+    self.remove_unpicklable_name(name)
+
+  def remove_global(self, name):
+    """Removes a global, if it exists.
+
+    Args:
+      name: string, the name of the global to remove
+    """
+    if name in self.global_names:
+      index = self.global_names.index(name)
+      del self.global_names[index]
+      del self.globals[index]
+
+  def globals_dict(self):
+    """Returns a dictionary view of the globals.
+    """
+    return dict((name, pickle.loads(val))
+                for name, val in zip(self.global_names, self.globals))
+
+  def add_unpicklable(self, statement, names):
+    """Adds a statement and list of names to the unpicklables.
+
+    Also removes the names from the globals.
+
+    Args:
+      statement: string, the statement that created new unpicklable global(s).
+      names: list of strings; the names of the globals created by the statement.
+    """
+    self.unpicklables.append(db.Text(statement))
+
+    for name in names:
+      self.remove_global(name)
+      if name not in self.unpicklable_names:
+        self.unpicklable_names.append(db.Text(name))
+
+  def remove_unpicklable_name(self, name):
+    """Removes a name from the list of unpicklable names, if it exists.
+
+    Args:
+      name: string, the name of the unpicklable global to remove
+    """
+    if name in self.unpicklable_names:
+      self.unpicklable_names.remove(name)
+
+
+class FrontPageHandler(webapp.RequestHandler):
+  """Creates a new session and renders the shell.html template.
+  """
+
+  def get(self):
+    # set up the session. TODO: garbage collect old shell sessions
+    session_key = self.request.get('session')
+    if session_key:
+      session = Session.get(session_key)
+    else:
+      # create a new session
+      session = Session()
+      session.unpicklables = [db.Text(line) for line in INITIAL_UNPICKLABLES]
+      session_key = session.put()
+
+    template_file = os.path.join(os.path.dirname(__file__), 'templates',
+                                 'shell.html')
+    session_url = '/?session=%s' % session_key
+    vars = { 'server_software': os.environ['SERVER_SOFTWARE'],
+             'python_version': sys.version,
+             'session': str(session_key),
+             'user': users.get_current_user(),
+             'login_url': users.create_login_url(session_url),
+             'logout_url': users.create_logout_url(session_url),
+             }
+    rendered = webapp.template.render(template_file, vars, debug=_DEBUG)
+    self.response.out.write(rendered)
+
+
+class StatementHandler(webapp.RequestHandler):
+  """Evaluates a python statement in a given session and returns the result.
+  """
+
+  def get(self):
+    self.response.headers['Content-Type'] = 'text/plain'
+
+    # extract the statement to be run
+    statement = self.request.get('statement')
+    if not statement:
+      return
+
+    # the python compiler doesn't like network line endings
+    statement = statement.replace('\r\n', '\n')
+
+    # add a couple newlines at the end of the statement. this makes
+    # single-line expressions such as 'class Foo: pass' evaluate happily.
+    statement += '\n\n'
+
+    # log and compile the statement up front
+    try:
+      logging.info('Compiling and evaluating:\n%s' % statement)
+      compiled = compile(statement, '<string>', 'single')
+    except:
+      self.response.out.write(traceback.format_exc())
+      return
+
+    # create a dedicated module to be used as this statement's __main__
+    statement_module = new.module('__main__')
+
+    # use this request's __builtin__, since it changes on each request.
+    # this is needed for import statements, among other things.
+    import __builtin__
+    statement_module.__builtins__ = __builtin__
+
+    # load the session from the datastore
+    session = Session.get(self.request.get('session'))
+
+    # swap in our custom module for __main__. then unpickle the session
+    # globals, run the statement, and re-pickle the session globals, all
+    # inside it.
+    old_main = sys.modules.get('__main__')
+    try:
+      sys.modules['__main__'] = statement_module
+      statement_module.__name__ = '__main__'
+
+      # re-evaluate the unpicklables
+      for code in session.unpicklables:
+        exec code in statement_module.__dict__
+
+      # re-initialize the globals
+      for name, val in session.globals_dict().items():
+        try:
+          statement_module.__dict__[name] = val
+        except:
+          msg = 'Dropping %s since it could not be unpickled.\n' % name
+          self.response.out.write(msg)
+          logging.warning(msg + traceback.format_exc())
+          session.remove_global(name)
+
+      # run!
+      old_globals = dict(statement_module.__dict__)
+      try:
+        old_stdout = sys.stdout
+        old_stderr = sys.stderr
+        try:
+          sys.stdout = self.response.out
+          sys.stderr = self.response.out
+          exec compiled in statement_module.__dict__
+        finally:
+          sys.stdout = old_stdout
+          sys.stderr = old_stderr
+      except:
+        self.response.out.write(traceback.format_exc())
+        return
+
+      # extract the new globals that this statement added
+      new_globals = {}
+      for name, val in statement_module.__dict__.items():
+        if name not in old_globals or val != old_globals[name]:
+          new_globals[name] = val
+
+      if True in [isinstance(val, UNPICKLABLE_TYPES)
+                  for val in new_globals.values()]:
+        # this statement added an unpicklable global. store the statement and
+        # the names of all of the globals it added in the unpicklables.
+        session.add_unpicklable(statement, new_globals.keys())
+        logging.debug('Storing this statement as an unpicklable.')
+
+      else:
+        # this statement didn't add any unpicklables. pickle and store the
+        # new globals back into the datastore.
+        for name, val in new_globals.items():
+          if not name.startswith('__'):
+            session.set_global(name, val)
+
+    finally:
+      sys.modules['__main__'] = old_main
+
+    session.put()
+
+
+def main():
+  application = webapp.WSGIApplication(
+    [('/', FrontPageHandler),
+     ('/shell.do', StatementHandler)], debug=_DEBUG)
+  wsgiref.handlers.CGIHandler().run(application)
+
+
+if __name__ == '__main__':
+  main()
diff --git a/tools/scripts/app_engine_server/gae_shell/static/shell.js b/tools/scripts/app_engine_server/gae_shell/static/shell.js
new file mode 100644 (file)
index 0000000..4aa1583
--- /dev/null
@@ -0,0 +1,195 @@
+// Copyright 2007 Google Inc.
+//
+// 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.
+
+/**
+ * @fileoverview
+ * Javascript code for the interactive AJAX shell.
+ *
+ * Part of http://code.google.com/p/google-app-engine-samples/.
+ *
+ * Includes a function (shell.runStatement) that sends the current python
+ * statement in the shell prompt text box to the server, and a callback
+ * (shell.done) that displays the results when the XmlHttpRequest returns.
+ *
+ * Also includes cross-browser code (shell.getXmlHttpRequest) to get an
+ * XmlHttpRequest.
+ */
+
+/**
+ * Shell namespace.
+ * @type {Object}
+ */
+var shell = {}
+
+/**
+ * The shell history. history is an array of strings, ordered oldest to
+ * newest. historyCursor is the current history element that the user is on.
+ *
+ * The last history element is the statement that the user is currently
+ * typing. When a statement is run, it's frozen in the history, a new history
+ * element is added to the end of the array for the new statement, and
+ * historyCursor is updated to point to the new element.
+ *
+ * @type {Array}
+ */
+shell.history = [''];
+
+/**
+ * See {shell.history}
+ * @type {number}
+ */
+shell.historyCursor = 0;
+
+/**
+ * A constant for the XmlHttpRequest 'done' state.
+ * @type Number
+ */
+shell.DONE_STATE = 4;
+
+/**
+ * A cross-browser function to get an XmlHttpRequest object.
+ *
+ * @return {XmlHttpRequest?} a new XmlHttpRequest
+ */
+shell.getXmlHttpRequest = function() {
+  if (window.XMLHttpRequest) {
+    return new XMLHttpRequest();
+  } else if (window.ActiveXObject) {
+    try {
+      return new ActiveXObject('Msxml2.XMLHTTP');
+    } catch(e) {
+      return new ActiveXObject('Microsoft.XMLHTTP');
+    }
+  }
+
+  return null;
+};
+
+/**
+ * This is the prompt textarea's onkeypress handler. Depending on the key that
+ * was pressed, it will run the statement, navigate the history, or update the
+ * current statement in the history.
+ *
+ * @param {Event} event the keypress event
+ * @return {Boolean} false to tell the browser not to submit the form.
+ */
+shell.onPromptKeyPress = function(event) {
+  var statement = document.getElementById('statement');
+
+  if (this.historyCursor == this.history.length - 1) {
+    // we're on the current statement. update it in the history before doing
+    // anything.
+    this.history[this.historyCursor] = statement.value;
+  }
+
+  // should we pull something from the history?
+  if (event.ctrlKey && event.keyCode == 38 /* up arrow */) {
+    if (this.historyCursor > 0) {
+      statement.value = this.history[--this.historyCursor];
+    }
+    return false;
+  } else if (event.ctrlKey && event.keyCode == 40 /* down arrow */) {
+    if (this.historyCursor < this.history.length - 1) {
+      statement.value = this.history[++this.historyCursor];
+    }
+    return false;
+  } else if (!event.altKey) {
+    // probably changing the statement. update it in the history.
+    this.historyCursor = this.history.length - 1;
+    this.history[this.historyCursor] = statement.value;
+  }
+
+  // should we submit?
+  var ctrlEnter = (document.getElementById('submit_key').value == 'ctrl-enter');
+  if (event.keyCode == 13 /* enter */ && !event.altKey && !event.shiftKey &&
+      event.ctrlKey == ctrlEnter) {
+    return this.runStatement();
+  }
+};
+
+/**
+ * The XmlHttpRequest callback. If the request succeeds, it adds the command
+ * and its resulting output to the shell history div.
+ *
+ * @param {XmlHttpRequest} req the XmlHttpRequest we used to send the current
+ *     statement to the server
+ */
+shell.done = function(req) {
+  if (req.readyState == this.DONE_STATE) {
+    var statement = document.getElementById('statement')
+    statement.className = 'prompt';
+
+    // add the command to the shell output
+    var output = document.getElementById('output');
+
+    output.value += '\n>>> ' + statement.value;
+    statement.value = '';
+
+    // add a new history element
+    this.history.push('');
+    this.historyCursor = this.history.length - 1;
+
+    // add the command's result
+    var result = req.responseText.replace(/^\s*|\s*$/g, '');  // trim whitespace
+    if (result != '')
+      output.value += '\n' + result;
+
+    // scroll to the bottom
+    output.scrollTop = output.scrollHeight;
+    if (output.createTextRange) {
+      var range = output.createTextRange();
+      range.collapse(false);
+      range.select();
+    }
+  }
+};
+
+/**
+ * This is the form's onsubmit handler. It sends the python statement to the
+ * server, and registers shell.done() as the callback to run when it returns.
+ *
+ * @return {Boolean} false to tell the browser not to submit the form.
+ */
+shell.runStatement = function() {
+  var form = document.getElementById('form');
+
+  // build a XmlHttpRequest
+  var req = this.getXmlHttpRequest();
+  if (!req) {
+    document.getElementById('ajax-status').innerHTML =
+        "<span class='error'>Your browser doesn't support AJAX. :(</span>";
+    return false;
+  }
+
+  req.onreadystatechange = function() { shell.done(req); };
+
+  // build the query parameter string
+  var params = '';
+  for (i = 0; i < form.elements.length; i++) {
+    var elem = form.elements[i];
+    if (elem.type != 'submit' && elem.type != 'button' && elem.id != 'caret') {
+      var value = escape(elem.value).replace(/\+/g, '%2B'); // escape ignores +
+      params += '&' + elem.name + '=' + value;
+    }
+  }
+
+  // send the request and tell the user.
+  document.getElementById('statement').className = 'prompt processing';
+  req.open(form.method, form.action + '?' + params, true);
+  req.setRequestHeader('Content-type',
+                       'application/x-www-form-urlencoded;charset=UTF-8');
+  req.send(null);
+
+  return false;
+};
diff --git a/tools/scripts/app_engine_server/gae_shell/static/spinner.gif b/tools/scripts/app_engine_server/gae_shell/static/spinner.gif
new file mode 100644 (file)
index 0000000..3e58d6e
Binary files /dev/null and b/tools/scripts/app_engine_server/gae_shell/static/spinner.gif differ
diff --git a/tools/scripts/app_engine_server/gae_shell/templates/shell.html b/tools/scripts/app_engine_server/gae_shell/templates/shell.html
new file mode 100644 (file)
index 0000000..123b200
--- /dev/null
@@ -0,0 +1,122 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8" />
+<title> Interactive Shell </title>
+<script type="text/javascript" src="/gae_shell/static/shell.js"></script>
+<style type="text/css">
+body {
+  font-family: monospace;
+  font-size: 10pt;
+}
+
+p {
+  margin: 0.5em;
+}
+
+.prompt, #output {
+  width: 45em;
+  border: 1px solid silver;
+  background-color: #f5f5f5;
+  font-size: 10pt;
+  margin: 0.5em;
+  padding: 0.5em;
+  padding-right: 0em;
+  overflow-x: hidden;
+}
+
+#toolbar {
+  margin-left: 0.5em;
+  padding-left: 0.5em;
+}
+
+#caret {
+  width: 2.5em;
+  margin-right: 0px;
+  padding-right: 0px;
+  border-right: 0px;
+}
+
+#statement {
+  width: 43em;
+  margin-left: -1em;
+  padding-left: 0px;
+  border-left: 0px;
+  background-position: top right;
+  background-repeat: no-repeat;
+}
+
+.processing {
+  background-image: url("/gae_shell/static/spinner.gif");
+}
+
+#ajax-status {
+  font-weight: bold;
+}
+
+.message {
+  color: #8AD;
+  font-weight: bold;
+  font-style: italic;
+}
+
+.error {
+  color: #F44;
+}
+
+.username {
+  font-weight: bold;
+}
+
+</style>
+</head>
+
+<body>
+
+<p> Interactive server-side Python shell for
+<a href="http://code.google.com/appengine/">Google App Engine</a>.
+(<a href="http://code.google.com/p/google-app-engine-samples/">source</a>)
+</p>
+
+<textarea id="output" rows="22" readonly="readonly">
+{{ server_software }}
+Python {{ python_version }}
+</textarea>
+
+<form id="form" action="shell.do" method="get">
+  <nobr>
+  <textarea class="prompt" id="caret" readonly="readonly" rows="4"
+            onfocus="document.getElementById('statement').focus()"
+            >&gt;&gt;&gt;</textarea>
+  <textarea class="prompt" name="statement" id="statement" rows="4"
+            onkeypress="return shell.onPromptKeyPress(event);"></textarea>
+  </nobr>
+  <input type="hidden" name="session" value="{{ session }}" />
+  <input type="submit" style="display: none" />
+</form>
+
+<p id="ajax-status"></p>
+
+<p id="toolbar">
+{% if user %}
+  <span class="username">{{ user.nickname }}</span>
+  (<a href="{{ logout_url }}">log out</a>)
+{% else %}
+  <a href="{{ login_url }}">log in</a>
+{% endif %}
+ | Ctrl-Up/Down for history |
+<select id="submit_key">
+  <option value="enter">Enter</option>
+  <option value="ctrl-enter" selected="selected">Ctrl-Enter</option>
+</select>
+<label for="submit_key">submits</label>
+</p>
+
+<script type="text/javascript">
+document.getElementById('statement').focus();
+</script>
+
+</body>
+</html>
+
diff --git a/tools/scripts/app_engine_server/index.yaml b/tools/scripts/app_engine_server/index.yaml
new file mode 100644 (file)
index 0000000..8e6046d
--- /dev/null
@@ -0,0 +1,12 @@
+indexes:
+
+# AUTOGENERATED
+
+# This index.yaml is automatically updated whenever the dev_appserver
+# detects that a new type of query is run.  If you want to manage the
+# index.yaml file manually, remove the above marker line (the line
+# saying "# AUTOGENERATED").  If you want to manage some indexes
+# manually, move them above the marker line.  The index.yaml file is
+# automatically uploaded to the admin console when you next deploy
+# your application using appcfg.py.
+
diff --git a/tools/scripts/app_engine_server/memcache_zipserve.py b/tools/scripts/app_engine_server/memcache_zipserve.py
new file mode 100644 (file)
index 0000000..e11cfc5
--- /dev/null
@@ -0,0 +1,412 @@
+#!/usr/bin/env python
+#
+# Copyright 2009 Google Inc.
+#
+# 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.
+#
+
+"""A class to serve pages from zip files and use memcache for performance.
+
+This contains a class and a function to create an anonymous instance of the
+class to serve HTTP GET requests. Memcache is used to increase response speed
+and lower processing cycles used in serving. Credit to Guido van Rossum and
+his implementation of zipserve which served as a reference as I wrote this.
+
+  MemcachedZipHandler: Class that serves request
+  create_handler: method to create instance of MemcachedZipHandler
+"""
+
+__author__ = 'jmatt@google.com (Justin Mattson)'
+
+import email.Utils
+import logging
+import mimetypes
+import time
+import zipfile
+
+from google.appengine.api import memcache
+from google.appengine.ext import webapp
+from google.appengine.ext.webapp import util
+
+
+def create_handler(zip_files, max_age=None, public=None):
+  """Factory method to create a MemcachedZipHandler instance.
+
+  Args:
+    zip_files: A list of file names, or a list of lists of file name, first
+        member of file mappings. See MemcachedZipHandler documentation for
+        more information about using the list of lists format
+    max_age: The maximum client-side cache lifetime
+    public: Whether this should be declared public in the client-side cache
+  Returns:
+    A MemcachedZipHandler wrapped in a pretty, anonymous bow for use with App
+    Engine
+
+  Raises:
+    ValueError: if the zip_files argument is not a list
+  """
+  # verify argument integrity. If the argument is passed in list format,
+  # convert it to list of lists format
+  
+  if zip_files and type(zip_files).__name__ == 'list':
+    num_items = len(zip_files)
+    while num_items > 0:
+      if type(zip_files[num_items - 1]).__name__ != 'list':
+        zip_files[num_items - 1] = [zip_files[num_items-1]]
+      num_items -= 1
+  else:
+    raise ValueError('File name arguments must be a list')
+
+  class HandlerWrapper(MemcachedZipHandler):
+    """Simple wrapper for an instance of MemcachedZipHandler.
+
+    I'm still not sure why this is needed
+    """
+    
+    def get(self, name):
+      self.zipfilenames = zip_files
+      self.TrueGet(name)
+      if max_age is not None:
+        MAX_AGE = max_age
+      if public is not None:
+        PUBLIC = public
+
+  return HandlerWrapper
+
+
+class MemcachedZipHandler(webapp.RequestHandler):
+  """Handles get requests for a given URL.
+
+  Serves a GET request from a series of zip files. As files are served they are
+  put into memcache, which is much faster than retreiving them from the zip
+  source file again. It also uses considerably fewer CPU cycles.
+  """
+  zipfile_cache = {}                # class cache of source zip files
+  MAX_AGE = 600                     # max client-side cache lifetime
+  PUBLIC = True                     # public cache setting
+  CACHE_PREFIX = 'cache://'         # memcache key prefix for actual URLs
+  NEG_CACHE_PREFIX = 'noncache://'  # memcache key prefix for non-existant URL
+
+  def TrueGet(self, name):
+    """The top-level entry point to serving requests.
+
+    Called 'True' get because it does the work when called from the wrapper
+    class' get method
+
+    Args:
+      name: URL requested
+
+    Returns:
+      None
+    """
+    name = self.PreprocessUrl(name)
+
+    # see if we have the page in the memcache
+    resp_data = self.GetFromCache(name)
+    if resp_data is None:
+      logging.info('Cache miss for %s', name)
+      resp_data = self.GetFromNegativeCache(name)
+      if resp_data is None:
+        resp_data = self.GetFromStore(name)
+
+        # IF we have the file, put it in the memcache
+        # ELSE put it in the negative cache
+        if resp_data is not None:
+          self.StoreOrUpdateInCache(name, resp_data)
+        else:
+          logging.info('Adding %s to negative cache, serving 404', name)
+          self.StoreInNegativeCache(name)
+          self.Write404Error()
+          return
+      else:
+        self.Write404Error()
+        return
+
+    content_type, encoding = mimetypes.guess_type(name)
+    if content_type:
+      self.response.headers['Content-Type'] = content_type
+    self.SetCachingHeaders()
+    self.response.out.write(resp_data)
+
+  def PreprocessUrl(self, name):
+    """Any preprocessing work on the URL when it comes it.
+
+    Put any work related to interpretting the incoming URL here. For example,
+    this is used to redirect requests for a directory to the index.html file
+    in that directory. Subclasses should override this method to do different
+    preprocessing.
+
+    Args:
+      name: The incoming URL
+
+    Returns:
+      The processed URL
+    """
+    # handle special case of requesting the domain itself
+    if not name:
+      name = 'index.html'
+
+    # determine if this is a request for a directory
+    final_path_segment = name
+    final_slash_offset = name.rfind('/')
+    if final_slash_offset != len(name) - 1:
+      final_path_segment = name[final_slash_offset + 1:]
+      if final_path_segment.find('.') == -1:
+        name = ''.join([name, '/'])
+
+    # if this is a directory, redirect to index.html
+    if name[len(name) - 1:] == '/':
+      return '%s%s' % (name, 'index.html')
+    else:
+      return name
+
+  def GetFromStore(self, file_path):
+    """Retrieve file from zip files.
+
+    Get the file from the source, it must not have been in the memcache. If
+    possible, we'll use the zip file index to quickly locate where the file
+    should be found. (See MapToFileArchive documentation for assumptions about
+    file ordering.) If we don't have an index or don't find the file where the
+    index says we should, look through all the zip files to find it.
+
+    Args:
+      file_path: the file that we're looking for
+
+    Returns:
+      The contents of the requested file
+    """
+    resp_data = None
+    file_itr = iter(self.zipfilenames)
+
+    # check the index, if we have one, to see what archive the file is in
+    archive_name = self.MapFileToArchive(file_path)
+    if not archive_name:
+      archive_name = file_itr.next()[0]
+    
+    while resp_data is None and archive_name:
+      zip_archive = self.LoadZipFile(archive_name)
+      if zip_archive:
+
+        # we expect some lookups will fail, and that's okay, 404s will deal
+        # with that
+        try:
+          resp_data = zip_archive.read(file_path)
+        except (KeyError, RuntimeError), err:
+          # no op
+          x = False
+        if resp_data is not None:
+          logging.info('%s read from %s', file_path, archive_name)
+          
+      try:
+        archive_name = file_itr.next()[0]
+      except (StopIteration), err:
+        archive_name = False
+
+    return resp_data
+
+  def LoadZipFile(self, zipfilename):
+    """Convenience method to load zip file.
+
+    Just a convenience method to load the zip file from the data store. This is
+    useful if we ever want to change data stores and also as a means of
+    dependency injection for testing. This method will look at our file cache
+    first, and then load and cache the file if there's a cache miss
+
+    Args:
+      zipfilename: the name of the zip file to load
+
+    Returns:
+      The zip file requested, or None if there is an I/O error
+    """
+    zip_archive = None
+    zip_archive = self.zipfile_cache.get(zipfilename)
+    if zip_archive is None:
+      try:
+        zip_archive = zipfile.ZipFile(zipfilename)
+        self.zipfile_cache[zipfilename] = zip_archive
+      except (IOError, RuntimeError), err:
+        logging.error('Can\'t open zipfile %s, cause: %s' % (zipfilename,
+                                                             err))
+    return zip_archive
+
+  def MapFileToArchive(self, file_path):
+    """Given a file name, determine what archive it should be in.
+
+    This method makes two critical assumptions.
+    (1) The zip files passed as an argument to the handler, if concatenated
+        in that same order, would result in a total ordering
+        of all the files. See (2) for ordering type.
+    (2) Upper case letters before lower case letters. The traversal of a
+        directory tree is depth first. A parent directory's files are added
+        before the files of any child directories
+
+    Args:
+      file_path: the file to be mapped to an archive
+
+    Returns:
+      The name of the archive where we expect the file to be
+    """
+    num_archives = len(self.zipfilenames)
+    while num_archives > 0:
+      target = self.zipfilenames[num_archives - 1]
+      if len(target) > 1:
+        if self.CompareFilenames(target[1], file_path) >= 0:
+          return target[0]
+      num_archives -= 1
+
+    return None
+
+  def CompareFilenames(self, file1, file2):
+    """Determines whether file1 is lexigraphically 'before' file2.
+
+    WARNING: This method assumes that paths are output in a depth-first,
+    with parent directories' files stored before childs'
+
+    We say that file1 is lexigraphically before file2 if the last non-matching
+    path segment of file1 is alphabetically before file2.
+    
+    Args:
+      file1: the first file path
+      file2: the second file path
+
+    Returns:
+      A positive number if file1 is before file2
+      A negative number if file2 is before file1
+      0 if filenames are the same
+    """
+    f1_segments = file1.split('/')
+    f2_segments = file2.split('/')
+
+    segment_ptr = 0
+    while (segment_ptr < len(f1_segments) and
+           segment_ptr < len(f2_segments) and
+           f1_segments[segment_ptr] == f2_segments[segment_ptr]):
+      segment_ptr += 1
+
+    if len(f1_segments) == len(f2_segments):
+
+      # we fell off the end, the paths much be the same
+      if segment_ptr == len(f1_segments):
+        return 0
+
+      # we didn't fall of the end, compare the segments where they differ
+      if f1_segments[segment_ptr] < f2_segments[segment_ptr]:
+        return 1
+      elif f1_segments[segment_ptr] > f2_segments[segment_ptr]:
+        return -1
+      else:
+        return 0
+
+      # the number of segments differs, we either mismatched comparing
+      # directories, or comparing a file to a directory
+    else:
+
+      # IF we were looking at the last segment of one of the paths,
+      # the one with fewer segments is first because files come before
+      # directories
+      # ELSE we just need to compare directory names
+      if (segment_ptr + 1 == len(f1_segments) or
+          segment_ptr + 1 == len(f2_segments)):
+        return len(f2_segments) - len(f1_segments)
+      else:
+        if f1_segments[segment_ptr] < f2_segments[segment_ptr]:
+          return 1
+        elif f1_segments[segment_ptr] > f2_segments[segment_ptr]:
+          return -1
+        else:
+          return 0
+
+  def SetCachingHeaders(self):
+    """Set caching headers for the request."""
+    max_age = self.MAX_AGE
+    self.response.headers['Expires'] = email.Utils.formatdate(
+        time.time() + max_age, usegmt=True)
+    cache_control = []
+    if self.PUBLIC:
+      cache_control.append('public')
+    cache_control.append('max-age=%d' % max_age)
+    self.response.headers['Cache-Control'] = ', '.join(cache_control)
+
+  def GetFromCache(self, filename):
+    """Get file from memcache, if available.
+
+    Args:
+      filename: The URL of the file to return
+
+    Returns:
+      The content of the file
+    """
+    return memcache.get('%s%s' % (self.CACHE_PREFIX, filename))
+
+  def StoreOrUpdateInCache(self, filename, data):
+    """Store data in the cache.
+
+    Store a piece of data in the memcache. Memcache has a maximum item size of
+    1*10^6 bytes. If the data is too large, fail, but log the failure. Future
+    work will consider compressing the data before storing or chunking it
+
+    Args:
+      filename: the name of the file to store
+      data: the data of the file
+
+    Returns:
+      None
+    """
+    try:
+      if not memcache.add('%s%s' % (self.CACHE_PREFIX, filename), data):
+        memcache.replace('%s%s' % (self.CACHE_PREFIX, filename), data)
+    except (ValueError), err:
+      logging.warning('Data size too large to cache\n%s' % err)
+
+  def Write404Error(self):
+    """Ouptut a simple 404 response."""
+    self.error(404)
+    self.response.out.write(
+        ''.join(['<html><head><title>404: Not Found</title></head>',
+                 '<body><b><h2>Error 404</h2><br/>',
+                 'File not found</b></body></html>']))
+
+  def StoreInNegativeCache(self, filename):
+    """If a non-existant URL is accessed, cache this result as well.
+
+    Future work should consider setting a maximum negative cache size to
+    prevent it from from negatively impacting the real cache.
+
+    Args:
+      filename: URL to add ot negative cache
+
+    Returns:
+      None
+    """
+    memcache.add('%s%s' % (self.NEG_CACHE_PREFIX, filename), -1)
+
+  def GetFromNegativeCache(self, filename):
+    """Retrieve from negative cache.
+
+    Args:
+      filename: URL to retreive
+
+    Returns:
+      The file contents if present in the negative cache.
+    """
+    return memcache.get('%s%s' % (self.NEG_CACHE_PREFIX, filename))
+
+
+def main():
+  application = webapp.WSGIApplication([('/([^/]+)/(.*)',
+                                         MemcachedZipHandler)])
+  util.run_wsgi_app(application)
+
+
+if __name__ == '__main__':
+  main()
index 89a1141..ebaa1c6 100755 (executable)
@@ -2,22 +2,36 @@
 
 function replace()
 {
-    echo replacing $1
-    rm -rf $UNZIPPED_BASE_DIR/$1
-    cp -rf $UNZIPPED_IMAGE_DIR/$1 $UNZIPPED_BASE_DIR/$1
+        echo replacing $1
+        rm $V -rf "$UNZIPPED_BASE_DIR"/$1
+        cp $V -rf "$UNZIPPED_IMAGE_DIR"/$1 "$UNZIPPED_BASE_DIR"/$1
 }
 
-BASE=$1
-IMAGES=$2
-OUTPUT=$3
+V=""
+Q="-q"
+if [ "$1" == "-v" ]; then
+    V="-v"
+    Q=""
+    shift
+fi
+
+NOZIP=""
+if [ "$1" == "-nozip" ]; then
+    NOZIP="1"
+    shift
+fi
 
-if [[ -z $BASE || -z $IMAGES || -z $OUTPUT ]] ; then
-    echo "usage: combine_sdks.sh BASE IMAGES OUTPUT"
+BASE="$1"
+IMAGES="$2"
+OUTPUT="$3"
+
+if [[ -z "$BASE" || -z "$IMAGES" || -z "$OUTPUT" ]] ; then
+    echo "usage: combine_sdks.sh [-v] [-nozip] BASE IMAGES OUTPUT"
     echo
     echo "  BASE and IMAGES should be sdk zip files.  The system image files,"
     echo "  emulator and other runtime files will be copied from IMAGES and"
     echo "  everything else will be copied from BASE.  All of this will be"
-    echo "  bundled into OUTPUT and zipped up again."
+    echo "  bundled into OUTPUT and zipped up again (unless -nozip is specified)."
     echo
     exit 1
 fi
@@ -26,15 +40,25 @@ TMP=$(mktemp -d)
 
 TMP_ZIP=tmp.zip
 
-BASE_DIR=$TMP/base
-IMAGES_DIR=$TMP/images
-OUTPUT_TMP_ZIP=$BASE_DIR/$TMP_ZIP
+# determine executable extension
+case `uname -s` in
+    *_NT-*)  # for Windows
+        EXE=.exe
+        ;;
+    *)
+        EXE=
+        ;;
+esac
+
+BASE_DIR="$TMP"/base
+IMAGES_DIR="$TMP"/images
+OUTPUT_TMP_ZIP="$BASE_DIR/$TMP_ZIP"
 
-unzip -q $BASE -d $BASE_DIR
-unzip -q $IMAGES -d $IMAGES_DIR
+unzip $Q "$BASE"   -d "$BASE_DIR"
+unzip $Q "$IMAGES" -d "$IMAGES_DIR"
 
-UNZIPPED_BASE_DIR=$(echo $BASE_DIR/*)
-UNZIPPED_IMAGE_DIR=$(echo $IMAGES_DIR/*)
+UNZIPPED_BASE_DIR=$(echo "$BASE_DIR"/*)
+UNZIPPED_IMAGE_DIR=$(echo "$IMAGES_DIR"/*)
 
 #
 # The commands to copy over the files that we want
@@ -42,21 +66,40 @@ UNZIPPED_IMAGE_DIR=$(echo $IMAGES_DIR/*)
 
 # replace tools/emulator # at this time we do not want the exe from SDK1.x
 replace tools/lib/images
+replace tools/lib/res
+replace tools/lib/fonts
+replace tools/lib/layoutlib.jar
 replace docs
 replace android.jar
 
+for i in widgets categories broadcast_actions service_actions; do
+    replace tools/lib/$i.txt
+done
+
+if [ -d "$UNZIPPED_BASE_DIR"/usb_driver ]; then
+    replace usb_driver
+fi
+
 #
 # end
 #
 
-pushd $BASE_DIR &> /dev/null
-    # rename the directory to the leaf minus the .zip of OUTPUT
-    LEAF=$(echo $OUTPUT | sed -e "s:.*\.zip/::" | sed -e "s:.zip$::")
-    mv * $LEAF
-    # zip it
-    zip -qr $TMP_ZIP $LEAF
-popd &> /dev/null
+if [ -z "$NOZIP" ]; then
+    pushd "$BASE_DIR" &> /dev/null
+        # rename the directory to the leaf minus the .zip of OUTPUT
+        LEAF=$(echo "$OUTPUT" | sed -e "s:.*\.zip/::" | sed -e "s:.zip$::")
+        mv * "$LEAF"
+        # zip it
+        zip $V -qr "$TMP_ZIP" "$LEAF"
+    popd &> /dev/null
+
+    cp $V "$OUTPUT_TMP_ZIP" "$OUTPUT"
+    echo "Combined SDK available at $OUTPUT"
+else
+    OUT_DIR="${OUTPUT//.zip/}"
+    mv $V "$BASE_DIR"/* "$OUT_DIR"
+    echo "Unzipped combined SDK available at $OUT_DIR"
+fi
 
-cp $OUTPUT_TMP_ZIP $OUTPUT
+rm $V -rf "$TMP"
 
-rm -rf $TMP
index 2db668e..f1531e0 100644 (file)
@@ -31,52 +31,67 @@ import java.util.Map.Entry;
  * <li>define flags for your actions.
  * </ul> 
  * <p/>
- * To use, call {@link #parseArgs(String[])} and then call {@link #getValue(String, String)}.
+ * To use, call {@link #parseArgs(String[])} and then
+ * call {@link #getValue(String, String, String)}.
  */
 public class CommandLineProcessor {
-    
-    /** Internal action name for all global flags. */
-    public final static String GLOBAL_FLAG = "global";
-    /** Internal action name for internally hidden flags.
-     *  This is currently used to store the requested action name. */
-    public final static String INTERNAL_FLAG = "internal";
 
+    /** Internal verb name for internally hidden flags. */
+    public final static String GLOBAL_FLAG_VERB = "@@internal@@";
+    
+    /** String to use when the verb doesn't need any object. */
+    public final static String NO_VERB_OBJECT = "";
+    
     /** The global help flag. */ 
     public static final String KEY_HELP = "help";
     /** The global verbose flag. */
     public static final String KEY_VERBOSE = "verbose";
     /** The global silent flag. */
     public static final String KEY_SILENT = "silent";
-    /** The internal action flag. */
-    public static final String KEY_ACTION = "action";
+    
+    /** Verb requested by the user. Null if none specified, which will be an error. */
+    private String mVerbRequested;
+    /** Direct object requested by the user. Can be null. */
+    private String mDirectObjectRequested;
 
-    /** List of available actions.
+    /**
+     * Action definitions.
      * <p/>
-     * Each entry must be a 2-string array with first the action name and then
-     * a description.
+     * Each entry is a string array with:
+     * <ul>
+     * <li> the verb.
+     * <li> a direct object (use #NO_VERB_OBJECT if there's no object).
+     * <li> a description.
+     * <li> an alternate form for the object (e.g. plural).
+     * </ul>
      */
     private final String[][] mActions;
-    /** The hash of all defined arguments.
+    
+    private static final int ACTION_VERB_INDEX = 0;
+    private static final int ACTION_OBJECT_INDEX = 1;
+    private static final int ACTION_DESC_INDEX = 2;
+    private static final int ACTION_ALT_OBJECT_INDEX = 3;
+
+    /**
+     * The map of all defined arguments.
      * <p/>
-     * The key is a string "action/longName".
+     * The key is a string "verb/directObject/longName".
      */
     private final HashMap<String, Arg> mArguments = new HashMap<String, Arg>();
+    /** Logger */
     private final ISdkLog mLog;
     
     public CommandLineProcessor(ISdkLog logger, String[][] actions) {
         mLog = logger;
         mActions = actions;
 
-        define(MODE.STRING, false, INTERNAL_FLAG, null, KEY_ACTION,
-                "Selected Action", null);
-
-        define(MODE.BOOLEAN, false, GLOBAL_FLAG, "v", KEY_VERBOSE,
+        define(MODE.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "v", KEY_VERBOSE,
                 "Verbose mode: errors, warnings and informational messages are printed.",
                 false);
-        define(MODE.BOOLEAN, false, GLOBAL_FLAG, "s", KEY_SILENT,
+        define(MODE.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "s", KEY_SILENT,
                 "Silent mode: only errors are printed out.",
                 false);
-        define(MODE.BOOLEAN, false, GLOBAL_FLAG, "h", KEY_HELP,
+        define(MODE.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "h", KEY_HELP,
                 "This help.",
                 false);
     }
@@ -86,47 +101,54 @@ public class CommandLineProcessor {
 
     /** Helper that returns true if --verbose was requested. */
     public boolean isVerbose() {
-        return ((Boolean) getValue(GLOBAL_FLAG, KEY_VERBOSE)).booleanValue();
+        return ((Boolean) getValue(GLOBAL_FLAG_VERB, NO_VERB_OBJECT, KEY_VERBOSE)).booleanValue();
     }
 
     /** Helper that returns true if --silent was requested. */
     public boolean isSilent() {
-        return ((Boolean) getValue(GLOBAL_FLAG, KEY_SILENT)).booleanValue();
+        return ((Boolean) getValue(GLOBAL_FLAG_VERB, NO_VERB_OBJECT, KEY_SILENT)).booleanValue();
     }
 
     /** Helper that returns true if --help was requested. */
     public boolean isHelpRequested() {
-        return ((Boolean) getValue(GLOBAL_FLAG, KEY_HELP)).booleanValue();
+        return ((Boolean) getValue(GLOBAL_FLAG_VERB, NO_VERB_OBJECT, KEY_HELP)).booleanValue();
+    }
+    
+    /** Returns the verb name from the command-line. Can be null. */
+    public String getVerb() {
+        return mVerbRequested;
     }
 
-    /** Helper that returns the requested action name. */
-    public String getActionRequested() {
-        return (String) getValue(INTERNAL_FLAG, KEY_ACTION);
+    /** Returns the direct object name from the command-line. Can be null. */
+    public String getDirectObject() {
+        return mDirectObjectRequested;
     }
     
     //------------------
     
     /**
      * Raw access to parsed parameter values.
-     * @param action The action name, including {@link #GLOBAL_FLAG} and {@link #INTERNAL_FLAG}
+     * @param verb The verb name, including {@link #GLOBAL_FLAG_VERB}.
+     * @param directObject The direct object name, including {@link #NO_VERB_OBJECT}.
      * @param longFlagName The long flag name for the given action.
      * @return The current value object stored in the parameter, which depends on the argument mode.
      */
-    public Object getValue(String action, String longFlagName) {
-        String key = action + "/" + longFlagName;
+    public Object getValue(String verb, String directObject, String longFlagName) {
+        String key = verb + "/" + directObject + "/" + longFlagName;
         Arg arg = mArguments.get(key);
         return arg.getCurrentValue();
     }
 
     /**
      * Internal setter for raw parameter value.
-     * @param action The action name, including {@link #GLOBAL_FLAG} and {@link #INTERNAL_FLAG}
+     * @param verb The verb name, including {@link #GLOBAL_FLAG_VERB}.
+     * @param directObject The direct object name, including {@link #NO_VERB_OBJECT}.
      * @param longFlagName The long flag name for the given action.
      * @param value The new current value object stored in the parameter, which depends on the
      *              argument mode.
      */
-    protected void setValue(String action, String longFlagName, Object value) {
-        String key = action + "/" + longFlagName;
+    protected void setValue(String verb, String directObject, String longFlagName, Object value) {
+        String key = verb + "/" + directObject + "/" + longFlagName;
         Arg arg = mArguments.get(key);
         arg.setCurrentValue(value);
     }
@@ -140,114 +162,172 @@ public class CommandLineProcessor {
      */
     public void parseArgs(String[] args) {
         String needsHelp = null;
-        String action = null;
-        
-        int n = args.length;
-        for (int i = 0; i < n; i++) {
-            Arg arg = null;
-            String a = args[i];
-            if (a.startsWith("--")) {
-                arg = findLongArg(action, a.substring(2));
-            } else if (a.startsWith("-")) {
-                arg = findShortArg(action, a.substring(1));
-            }
-            
-            // Not a keyword and we don't have an action yet, this should be an action
-            if (arg == null && action == null) {
-
-                if (a.startsWith("-")) {
-                    // Got a keyword but not valid for global flags
-                    needsHelp = String.format(
-                            "Flag '%1$s' is not a valid global flag. Did you mean to specify it after the action name?",
-                            a, action);
-                    break;
-                }
-
-                for (String[] actionDesc : mActions) {
-                    if (actionDesc[0].equals(a)) {
-                        action = a;
-                        break;
-                    }
-                }
-                
-                if (action == null) {
-                    needsHelp = String.format(
-                            "Expected action name after global parameters but found %1$s instead.",
-                            a);
-                    break;
+        String verb = null;
+        String directObject = null;
+
+        try {
+            int n = args.length;
+            for (int i = 0; i < n; i++) {
+                Arg arg = null;
+                String a = args[i];
+                if (a.startsWith("--")) {
+                    arg = findLongArg(verb, directObject, a.substring(2));
+                } else if (a.startsWith("-")) {
+                    arg = findShortArg(verb, directObject, a.substring(1));
                 }
-            } else if (arg == null && action != null) {
-                // Got a keyword but not valid for the current action
-                needsHelp = String.format(
-                        "Flag '%1$s' is not valid for action '%2$s'.",
-                        a, action);
-                break;
                 
-            } else if (arg != null) {
-                // Process keyword
-                String error = null;
-                if (arg.getMode().needsExtra()) {
-                    if (++i >= n) {
-                        needsHelp = String.format("Missing argument for flag %1$s.", a);
-                        break;
+                // No matching argument name found
+                if (arg == null) {
+                    // Does it looks like a dashed parameter?
+                    if (a.startsWith("-")) {
+                        if (verb == null || directObject == null) {
+                            // It looks like a dashed parameter and we don't have a a verb/object
+                            // set yet, the parameter was just given too early.
+    
+                            needsHelp = String.format(
+                                "Flag '%1$s' is not a valid global flag. Did you mean to specify it after the verb/object name?",
+                                a);
+                            return;
+                        } else {
+                            // It looks like a dashed parameter and but it is unknown by this
+                            // verb-object combination
+                            
+                            needsHelp = String.format(
+                                    "Flag '%1$s' is not valid for '%2$s %3$s'.",
+                                    a, verb, directObject);
+                            return;
+                        }
                     }
                     
-                    error = arg.getMode().process(arg, args[i]);
-                } else {
-                    error = arg.getMode().process(arg, null);
-
-                    // If we just toggled help, we want to exit now without printing any error.
-                    // We do this test here only when a Boolean flag is toggled since booleans
-                    // are the only flags that don't take parameters and help is a boolean.
-                    if (isHelpRequested()) {
-                        printHelpAndExit(null);
-                        // The call above should terminate however in unit tests we override
-                        // it so we still need to return here.
+                    if (verb == null) {
+                        // Fill verb first. Find it.
+                        for (String[] actionDesc : mActions) {
+                            if (actionDesc[ACTION_VERB_INDEX].equals(a)) {
+                                verb = a;
+                                break;
+                            }
+                        }
+                        
+                        // Error if it was not a valid verb
+                        if (verb == null) {
+                            needsHelp = String.format(
+                                "Expected verb after global parameters but found '%1$s' instead.",
+                                a);
+                            return;
+                        }
+    
+                    } else if (directObject == null) {
+                        // Then fill the direct object. Find it.
+                        for (String[] actionDesc : mActions) {
+                            if (actionDesc[ACTION_VERB_INDEX].equals(verb)) {
+                                if (actionDesc[ACTION_OBJECT_INDEX].equals(a)) {
+                                    directObject = a;
+                                    break;
+                                } else if (actionDesc.length > ACTION_ALT_OBJECT_INDEX &&
+                                        actionDesc[ACTION_ALT_OBJECT_INDEX].equals(a)) {
+                                    // if the alternate form exist and is used, we internally
+                                    // only memorize the default direct object form.
+                                    directObject = actionDesc[ACTION_OBJECT_INDEX];
+                                    break;
+                                }
+                            }
+                        }
+                        
+                        // Error if it was not a valid object for that verb
+                        if (directObject == null) {
+                            needsHelp = String.format(
+                                "Expected verb after global parameters but found '%1$s' instead.",
+                                a);
+                            return;
+                            
+                        }
+                    }
+                } else if (arg != null) {
+                    // Process keyword
+                    String error = null;
+                    if (arg.getMode().needsExtra()) {
+                        if (++i >= n) {
+                            needsHelp = String.format("Missing argument for flag %1$s.", a);
+                            return;
+                        }
+                        
+                        error = arg.getMode().process(arg, args[i]);
+                    } else {
+                        error = arg.getMode().process(arg, null);
+    
+                        // If we just toggled help, we want to exit now without printing any error.
+                        // We do this test here only when a Boolean flag is toggled since booleans
+                        // are the only flags that don't take parameters and help is a boolean.
+                        if (isHelpRequested()) {
+                            printHelpAndExit(null);
+                            // The call above should terminate however in unit tests we override
+                            // it so we still need to return here.
+                            return;
+                        }
+                    }
+                    
+                    if (error != null) {
+                        needsHelp = String.format("Invalid usage for flag %1$s: %2$s.", a, error);
                         return;
                     }
                 }
-                
-                if (error != null) {
-                    needsHelp = String.format("Invalid usage for flag %1$s: %2$s.", a, error);
-                    break;
-                }
             }
-        }
         
-        if (needsHelp == null) {
-            if (action == null) {
-                needsHelp = "Missing action name.";
-            } else {
-                // Validate that all mandatory arguments are non-null for this action
-                String missing = null;
-                boolean plural = false;
-                for (Entry<String, Arg> entry : mArguments.entrySet()) {
-                    Arg arg = entry.getValue();
-                    if (arg.getAction().equals(action)) {
-                        if (arg.isMandatory() && arg.getCurrentValue() == null) {
-                            if (missing == null) {
-                                missing = "--" + arg.getLongArg();
-                            } else {
-                                missing += ", --" + arg.getLongArg();
-                                plural = true;
+            if (needsHelp == null) {
+                if (verb == null) {
+                    needsHelp = "Missing verb name.";
+                } else {
+                    if (directObject == null) {
+                        // Make sure this verb has an optional direct object
+                        for (String[] actionDesc : mActions) {
+                            if (actionDesc[ACTION_VERB_INDEX].equals(verb) &&
+                                    actionDesc[ACTION_OBJECT_INDEX].equals(NO_VERB_OBJECT)) {
+                                directObject = NO_VERB_OBJECT;
+                                break;
                             }
                         }
+    
+                        if (directObject == null) {
+                            needsHelp = String.format("Missing object name for verb '%1$s'.", verb);
+                            return;
+                        }
+                    }
+                    
+                    // Validate that all mandatory arguments are non-null for this action
+                    String missing = null;
+                    boolean plural = false;
+                    for (Entry<String, Arg> entry : mArguments.entrySet()) {
+                        Arg arg = entry.getValue();
+                        if (arg.getVerb().equals(verb) &&
+                                arg.getDirectObject().equals(directObject)) {
+                            if (arg.isMandatory() && arg.getCurrentValue() == null) {
+                                if (missing == null) {
+                                    missing = "--" + arg.getLongArg();
+                                } else {
+                                    missing += ", --" + arg.getLongArg();
+                                    plural = true;
+                                }
+                            }
+                        }
+                    }
+    
+                    if (missing != null) {
+                        needsHelp  = String.format(
+                                "The %1$s %2$s must be defined for action '%3$s %4$s'",
+                                plural ? "parameters" : "parameter",
+                                missing,
+                                verb,
+                                directObject);
                     }
-                }
 
-                if (missing != null) {
-                    needsHelp  = String.format("The %1$s %2$s must be defined for action '%3$s'",
-                            plural ? "parameters" : "parameter",
-                            missing,
-                            action);
+                    mVerbRequested = verb;
+                    mDirectObjectRequested = directObject;
                 }
-
-                setValue(INTERNAL_FLAG, KEY_ACTION, action);
             }
-        }
-
-        if (needsHelp != null) {
-            printHelpAndExitForAction(action, needsHelp);
+        } finally {
+            if (needsHelp != null) {
+                printHelpAndExitForAction(verb, directObject, needsHelp);
+            }
         }
     }
     
@@ -255,11 +335,14 @@ public class CommandLineProcessor {
      * Finds an {@link Arg} given an action name and a long flag name.
      * @return The {@link Arg} found or null.
      */
-    protected Arg findLongArg(String action, String longName) {
-        if (action == null) {
-            action = GLOBAL_FLAG;
+    protected Arg findLongArg(String verb, String directObject, String longName) {
+        if (verb == null) {
+            verb = GLOBAL_FLAG_VERB;
+        }
+        if (directObject == null) {
+            directObject = NO_VERB_OBJECT;
         }
-        String key = action + "/" + longName;
+        String key = verb + "/" + directObject + "/" + longName;
         return mArguments.get(key);
     }
 
@@ -267,14 +350,17 @@ public class CommandLineProcessor {
      * Finds an {@link Arg} given an action name and a short flag name.
      * @return The {@link Arg} found or null.
      */
-    protected Arg findShortArg(String action, String shortName) {
-        if (action == null) {
-            action = GLOBAL_FLAG;
+    protected Arg findShortArg(String verb, String directObject, String shortName) {
+        if (verb == null) {
+            verb = GLOBAL_FLAG_VERB;
+        }
+        if (directObject == null) {
+            directObject = NO_VERB_OBJECT;
         }
 
         for (Entry<String, Arg> entry : mArguments.entrySet()) {
             Arg arg = entry.getValue();
-            if (arg.getAction().equals(action)) {
+            if (arg.getVerb().equals(verb) && arg.getDirectObject().equals(directObject)) {
                 if (shortName.equals(arg.getShortArg())) {
                     return arg;
                 }
@@ -291,18 +377,22 @@ public class CommandLineProcessor {
      * @param args Arguments for String.format
      */
     public void printHelpAndExit(String errorFormat, Object... args) {
-        printHelpAndExitForAction(null /*actionFilter*/, errorFormat, args);
+        printHelpAndExitForAction(null /*verb*/, null /*directObject*/, errorFormat, args);
     }
     
     /**
      * Prints the help/usage and exits.
      * 
-     * @param actionFilter If null, displays help for all actions. If not null, display help only
-     *          for that specific action. In all cases also display general usage and action list.
+     * @param verb If null, displays help for all verbs. If not null, display help only
+     *          for that specific verb. In all cases also displays general usage and action list.
+     * @param directObject If null, displays help for all verb objects.
+     *          If not null, displays help only for that specific action
+     *          In all cases also display general usage and action list.
      * @param errorFormat Optional error message to print prior to usage using String.format 
      * @param args Arguments for String.format
      */
-    public void printHelpAndExitForAction(String actionFilter, String errorFormat, Object... args) {
+    public void printHelpAndExitForAction(String verb, String directObject,
+            String errorFormat, Object... args) {
         if (errorFormat != null) {
             stderr(errorFormat, args);
         }
@@ -316,25 +406,27 @@ public class CommandLineProcessor {
             "  android [global options] action [action options]\n" +
             "\n" +
             "Global options:");
-        listOptions(GLOBAL_FLAG);
+        listOptions(GLOBAL_FLAG_VERB, NO_VERB_OBJECT);
 
-        stdout("\nValid actions:");
+        stdout("\nValid actions are composed of a verb and an optional direct object:");
         for (String[] action : mActions) {
-            String filler = "";
-            int len = action[0].length();
-            if (len < 10) {
-                filler = "          ".substring(len);
-            }
             
-            stdout("- %1$s:%2$s %3$s", action[0], filler, action[1]);
+            stdout("- %1$6s %2$-7s: %3$s",
+                    action[ACTION_VERB_INDEX],
+                    action[ACTION_OBJECT_INDEX],
+                    action[ACTION_DESC_INDEX]);
         }
         
         for (String[] action : mActions) {
-            if (actionFilter == null || actionFilter.equals(action[0])) {
-                stdout("\nAction \"%1$s\":", action[0]);
-                stdout("  %1$s", action[1]);
-                stdout("Options:");
-                listOptions(action[0]);
+            if (verb == null || verb.equals(action[ACTION_VERB_INDEX])) {
+                if (directObject == null || directObject.equals(action[ACTION_OBJECT_INDEX])) {
+                    stdout("\nAction \"%1$s %2$s\":",
+                            action[ACTION_VERB_INDEX],
+                            action[ACTION_OBJECT_INDEX]);
+                    stdout("  %1$s", action[ACTION_DESC_INDEX]);
+                    stdout("Options:");
+                    listOptions(action[ACTION_VERB_INDEX], action[ACTION_OBJECT_INDEX]);
+                }
             }
         }
         
@@ -344,11 +436,11 @@ public class CommandLineProcessor {
     /**
      * Internal helper to print all the option flags for a given action name.
      */
-    protected void listOptions(String action) {
+    protected void listOptions(String verb, String directObject) {
         int numOptions = 0;
         for (Entry<String, Arg> entry : mArguments.entrySet()) {
             Arg arg = entry.getValue();
-            if (arg.getAction().equals(action)) {
+            if (arg.getVerb().equals(verb) && arg.getDirectObject().equals(directObject)) {
                 
                 String value = "";
                 if (arg.getDefaultValue() instanceof String[]) {
@@ -483,21 +575,22 @@ public class CommandLineProcessor {
      * or a String array (in which case the first item is the current by default.)  
      */
     static class Arg {
-        private final String mAction;
+        private final String mVerb;
+        private final String mDirectObject;
         private final String mShortName;
         private final String mLongName;
         private final String mDescription;
         private final Object mDefaultValue;
-        private Object mCurrentValue;
         private final MODE mMode;
         private final boolean mMandatory;
+        private Object mCurrentValue;
 
         /**
          * Creates a new argument flag description.
          * 
          * @param mode The {@link MODE} for the argument.
          * @param mandatory True if this argument is mandatory for this action. 
-         * @param action The action name. Can be #GLOBAL_FLAG or #INTERNAL_FLAG.
+         * @param directObject The action name. Can be #NO_VERB_OBJECT or #INTERNAL_FLAG.
          * @param shortName The one-letter short argument name. Cannot be empty nor null.
          * @param longName The long argument name. Cannot be empty nor null.
          * @param description The description. Cannot be null.
@@ -505,14 +598,16 @@ public class CommandLineProcessor {
          */
         public Arg(MODE mode,
                    boolean mandatory,
-                   String action,
+                   String verb,
+                   String directObject,
                    String shortName,
                    String longName,
                    String description,
                    Object defaultValue) {
             mMode = mode;
             mMandatory = mandatory;
-            mAction = action;
+            mVerb = verb;
+            mDirectObject = directObject;
             mShortName = shortName;
             mLongName = longName;
             mDescription = description;
@@ -540,8 +635,12 @@ public class CommandLineProcessor {
             return mDescription;
         }
         
-        public String getAction() {
-            return mAction;
+        public String getVerb() {
+            return mVerb;
+        }
+
+        public String getDirectObject() {
+            return mDirectObject;
         }
         
         public Object getDefaultValue() {
@@ -565,7 +664,8 @@ public class CommandLineProcessor {
      * Internal helper to define a new argument for a give action.
      * 
      * @param mode The {@link MODE} for the argument.
-     * @param action The action name. Can be #GLOBAL_FLAG or #INTERNAL_FLAG.
+     * @param verb The verb name. Can be #INTERNAL_VERB.
+     * @param directObject The action name. Can be #NO_VERB_OBJECT or #INTERNAL_FLAG.
      * @param shortName The one-letter short argument name. Cannot be empty nor null.
      * @param longName The long argument name. Cannot be empty nor null.
      * @param description The description. Cannot be null.
@@ -573,14 +673,19 @@ public class CommandLineProcessor {
      */
     protected void define(MODE mode,
             boolean mandatory,
-            String action,
+            String verb,
+            String directObject,
             String shortName, String longName,
             String description, Object defaultValue) {
         assert(mandatory || mode == MODE.BOOLEAN); // a boolean mode cannot be mandatory
         
-        String key = action + "/" + longName;
+        if (directObject == null) {
+            directObject = NO_VERB_OBJECT;
+        }
+        
+        String key = verb + "/" + directObject + "/" + longName;
         mArguments.put(key, new Arg(mode, mandatory,
-                action, shortName, longName, description, defaultValue));
+                verb, directObject, shortName, longName, description, defaultValue));
     }
 
     /**
index 1544f5b..7965e87 100644 (file)
@@ -23,12 +23,12 @@ import com.android.sdklib.ISdkLog;
 import com.android.sdklib.SdkConstants;
 import com.android.sdklib.SdkManager;
 import com.android.sdklib.IAndroidTarget.IOptionalLibrary;
+import com.android.sdklib.avd.AvdManager;
+import com.android.sdklib.avd.HardwareProperties;
+import com.android.sdklib.avd.AvdManager.AvdInfo;
+import com.android.sdklib.avd.HardwareProperties.HardwareProperty;
 import com.android.sdklib.project.ProjectCreator;
 import com.android.sdklib.project.ProjectCreator.OutputLevel;
-import com.android.sdklib.vm.HardwareProperties;
-import com.android.sdklib.vm.VmManager;
-import com.android.sdklib.vm.HardwareProperties.HardwareProperty;
-import com.android.sdklib.vm.VmManager.VmInfo;
 
 import java.io.File;
 import java.io.IOException;
@@ -56,8 +56,8 @@ class Main {
     private ISdkLog mSdkLog;
     /** The SDK manager parses the SDK folder and gives access to the content. */
     private SdkManager mSdkManager;
-    /** Virtual Machine manager to access the list of VMs or create new ones. */
-    private VmManager mVmManager;
+    /** Virtual Machine manager to access the list of AVDs or create new ones. */
+    private AvdManager mAvdManager;
     /** Command-line processor with options specific to SdkManager. */
     private SdkCommandLine mSdkCommandLine;
     /** The working directory, either null or set to an existing absolute canonical directory. */
@@ -183,26 +183,31 @@ class Main {
      * Actually do an action...
      */
     private void doAction() {
-        String action = mSdkCommandLine.getActionRequested();
+        String verb = mSdkCommandLine.getVerb();
+        String directObject = mSdkCommandLine.getDirectObject();
         
-        if (SdkCommandLine.ACTION_LIST.equals(action)) {
+        if (SdkCommandLine.VERB_LIST.equals(verb)) {
             // list action.
-            if (SdkCommandLine.ARG_TARGET.equals(mSdkCommandLine.getListFilter())) {
+            if (SdkCommandLine.OBJECT_TARGET.equals(directObject)) {
                 displayTargetList();
-            } else if (SdkCommandLine.ARG_VM.equals(mSdkCommandLine.getListFilter())) {
-                displayVmList();
+            } else if (SdkCommandLine.OBJECT_AVD.equals(directObject)) {
+                displayAvdList();
             } else {
                 displayTargetList();
-                displayVmList();
+                displayAvdList();
             }
-        } else if (SdkCommandLine.ACTION_NEW_VM.equals(action)) {
-            createVm();
-        } else if (SdkCommandLine.ACTION_NEW_PROJECT.equals(action)) {
+
+        } else if (SdkCommandLine.VERB_CREATE.equals(verb) &&
+                SdkCommandLine.OBJECT_AVD.equals(directObject)) {
+            createAvd();
+
+        } else if (SdkCommandLine.VERB_CREATE.equals(verb) &&
+                SdkCommandLine.OBJECT_PROJECT.equals(directObject)) {
             // get the target and try to resolve it.
-            int targetId = mSdkCommandLine.getNewProjectTargetId();
+            int targetId = mSdkCommandLine.getCreateProjectTargetId();
             IAndroidTarget[] targets = mSdkManager.getTargets();
             if (targetId < 1 || targetId > targets.length) {
-                errorAndExit("Target id is not valid. Use '%s list -f target' to get the target Ids.",
+                errorAndExit("Target id is not valid. Use '%s list targets' to get the target ids.",
                         SdkConstants.androidCmdName());
             }
             IAndroidTarget target = targets[targetId - 1];
@@ -213,22 +218,24 @@ class Main {
                             OutputLevel.NORMAL,
                     mSdkLog);
 
-            String projectDir = getProjectLocation(mSdkCommandLine.getNewProjectLocation());
+            String projectDir = getProjectLocation(mSdkCommandLine.getCreateProjectLocation());
             
             creator.createProject(projectDir,
-                    mSdkCommandLine.getNewProjectName(),
-                    mSdkCommandLine.getNewProjectPackage(),
-                    mSdkCommandLine.getNewProjectActivity(),
+                    mSdkCommandLine.getCreateProjectName(),
+                    mSdkCommandLine.getCreateProjectPackage(),
+                    mSdkCommandLine.getCreateProjectActivity(),
                     target,
                     false /* isTestProject*/);
-        } else if (SdkCommandLine.ACTION_UPDATE_PROJECT.equals(action)) {
+
+        } else if (SdkCommandLine.VERB_UPDATE.equals(verb) &&
+                SdkCommandLine.OBJECT_PROJECT.equals(directObject)) {
             // get the target and try to resolve it.
             IAndroidTarget target = null;
             int targetId = mSdkCommandLine.getUpdateProjectTargetId();
             if (targetId >= 0) {
                 IAndroidTarget[] targets = mSdkManager.getTargets();
                 if (targetId < 1 || targetId > targets.length) {
-                    errorAndExit("Target id is not valid. Use '%s list -f target' to get the target Ids.",
+                    errorAndExit("Target id is not valid. Use '%s list targets' to get the target ids.",
                             SdkConstants.androidCmdName());
                 }
                 target = targets[targetId - 1];
@@ -340,20 +347,20 @@ class Main {
     }
     
     /**
-     * Displays the list of available VMs.
+     * Displays the list of available AVDs.
      */
-    private void displayVmList() {
+    private void displayAvdList() {
         try {
-            mVmManager = new VmManager(mSdkManager, null /* sdklog */);
+            mAvdManager = new AvdManager(mSdkManager, null /* sdklog */);
 
-            mSdkLog.printf("Available Android VMs:\n");
+            mSdkLog.printf("Available Android Virtual Devices:\n");
 
             int index = 1;
-            for (VmInfo info : mVmManager.getVms()) {
+            for (AvdInfo info : mAvdManager.getAvds()) {
                 mSdkLog.printf("[%d] %s\n", index, info.getName());
                 mSdkLog.printf("    Path: %s\n", info.getPath());
 
-                // get the target of the Vm
+                // get the target of the AVD
                 IAndroidTarget target = info.getTarget();
                 if (target.isPlatform()) {
                     mSdkLog.printf("    Target: %s (API level %d)\n", target.getName(),
@@ -373,57 +380,85 @@ class Main {
     }
     
     /**
-     * Creates a new VM. This is a text based creation with command line prompt.
+     * Creates a new AVD. This is a text based creation with command line prompt.
      */
-    private void createVm() {
+    private void createAvd() {
         // find a matching target
-        int targetId = mSdkCommandLine.getNewVmTargetId();
+        int targetId = mSdkCommandLine.getCreateAvdTargetId();
         IAndroidTarget target = null;
         
         if (targetId >= 1 && targetId <= mSdkManager.getTargets().length) {
             target = mSdkManager.getTargets()[targetId-1]; // target it is 1-based
         } else {
-            errorAndExit("Target id is not valid. Use '%s list -f target' to get the target Ids.",
+            errorAndExit("Target id is not valid. Use '%s list targets' to get the target ids.",
                     SdkConstants.androidCmdName());
         }
 
         try {
-            mVmManager = new VmManager(mSdkManager, mSdkLog);
+            boolean removePrevious = false;
+            mAvdManager = new AvdManager(mSdkManager, mSdkLog);
 
-            String vmName = mSdkCommandLine.getNewVmName();
-            VmInfo info = mVmManager.getVm(vmName);
+            String avdName = mSdkCommandLine.getCreateAvdName();
+            AvdInfo info = mAvdManager.getAvd(avdName);
             if (info != null) {
-                errorAndExit("VM %s already exists.", vmName);
-            } else {
-                String vmParentFolder = mSdkCommandLine.getNewVmLocation();
-                if (vmParentFolder == null) {
-                    vmParentFolder = AndroidLocation.getFolder() + AndroidLocation.FOLDER_VMS;
+                if (mSdkCommandLine.getCreateAvdForce()) {
+                    removePrevious = true;
+                    mSdkLog.warning(
+                            "Android Virtual Device '%s' already exists and will be replaced.",
+                            avdName);
+                } else {
+                    errorAndExit("Android Virtual Device '%s' already exists.", avdName);
+                    return;
                 }
+            }
 
-                Map<String, String> hardwareConfig = null;
-                if (target.isPlatform()) {
-                    try {
-                        hardwareConfig = promptForHardware(target);
-                    } catch (IOException e) {
-                        errorAndExit(e.getMessage());
-                    }
+            String avdParentFolder = mSdkCommandLine.getCreateAvdLocation();
+            if (avdParentFolder == null) {
+                avdParentFolder = AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD;
+            }
+
+            Map<String, String> hardwareConfig = null;
+            if (target.isPlatform()) {
+                try {
+                    hardwareConfig = promptForHardware(target);
+                } catch (IOException e) {
+                    errorAndExit(e.getMessage());
                 }
+            }
+            
+            AvdInfo oldAvdInfo = null;
+            if (removePrevious) {
+                oldAvdInfo = mAvdManager.getAvd(avdName);
+            }
 
-                mVmManager.createVm(vmParentFolder,
-                        mSdkCommandLine.getNewVmName(),
-                        target,
-                        mSdkCommandLine.getNewVmSkin(),
-                        mSdkCommandLine.getNewVmSdCard(),
-                        hardwareConfig,
-                        mSdkLog);
+            AvdInfo newAvdInfo = mAvdManager.createAvd(avdParentFolder,
+                    avdName,
+                    target,
+                    mSdkCommandLine.getCreateAvdSkin(),
+                    mSdkCommandLine.getCreateAvdSdCard(),
+                    hardwareConfig,
+                    removePrevious,
+                    mSdkLog);
+            
+            if (newAvdInfo != null && 
+                    oldAvdInfo != null &&
+                    !oldAvdInfo.getPath().equals(newAvdInfo.getPath())) {
+                mSdkLog.warning("Removing previous AVD directory at %s", oldAvdInfo.getPath());
+                // Remove the old data directory
+                File dir = new File(oldAvdInfo.getPath());
+                mAvdManager.recursiveDelete(dir);
+                dir.delete();
+                // Remove old avd info from manager
+                mAvdManager.removeAvd(oldAvdInfo);
             }
+            
         } catch (AndroidLocationException e) {
             errorAndExit(e.getMessage());
         }
     }
 
     /**
-     * Prompts the user to setup a hardware config for a Platform-based VM.
+     * Prompts the user to setup a hardware config for a Platform-based AVD.
      * @throws IOException 
      */
     private Map<String, String> promptForHardware(IAndroidTarget createTarget) throws IOException {
index 08626a8..fe93396 100644 (file)
@@ -25,152 +25,218 @@ import com.android.sdklib.SdkManager;
  */
 public class SdkCommandLine extends CommandLineProcessor {
 
-    public static final String ARG_ALIAS = "alias";
+    public final static String VERB_LIST   = "list";
+    public final static String VERB_CREATE = "create";
+    public final static String VERB_RENAME = "rename";
+    public final static String VERB_MOVE   = "move";
+    public final static String VERB_DELETE = "delete";
+    public final static String VERB_UPDATE = "update";
+
+    public static final String OBJECT_AVD      = "avd";
+    public static final String OBJECT_AVDS     = "avds";
+    public static final String OBJECT_TARGET   = "target";
+    public static final String OBJECT_TARGETS  = "targets";
+    public static final String OBJECT_PROJECT  = "project";
+
+    public static final String ARG_ALIAS    = "alias";
     public static final String ARG_ACTIVITY = "activity";
-    public static final String ARG_VM = "vm";
-    public static final String ARG_TARGET = "target";
-    public static final String ARG_ALL = "all";
+
+    public static final String KEY_ACTIVITY  = ARG_ACTIVITY;
+    public static final String KEY_PACKAGE   = "package";
+    public static final String KEY_MODE      = "mode";
+    public static final String KEY_TARGET_ID = OBJECT_TARGET;
+    public static final String KEY_NAME      = "name";
+    public static final String KEY_PATH      = "path";
+    public static final String KEY_FILTER    = "filter";
+    public static final String KEY_SKIN      = "skin";
+    public static final String KEY_SDCARD    = "sdcard";
+    public static final String KEY_FORCE     = "force";
+
+    /**
+     * Action definitions for SdkManager command line.
+     * <p/>
+     * Each entry is a string array with:
+     * <ul>
+     * <li> the verb.
+     * <li> an object (use #NO_VERB_OBJECT if there's no object).
+     * <li> a description.
+     * <li> an alternate form for the object (e.g. plural).
+     * </ul>
+     */
+    private final static String[][] ACTIONS = {
+            { VERB_LIST,
+                NO_VERB_OBJECT,
+                "Lists existing targets or virtual devices." },
+            { VERB_LIST,
+                OBJECT_AVD,
+                "Lists existing Android Virtual Devices.",
+                OBJECT_AVDS },
+            { VERB_LIST,
+                OBJECT_TARGET,
+                "Lists existing targets.",
+                OBJECT_TARGETS },
     
-    public static final String KEY_ACTIVITY = ARG_ACTIVITY;
-    public static final String KEY_PACKAGE = "package";
-    public static final String KEY_MODE = "mode";
-    public static final String KEY_TARGET_ID = ARG_TARGET;
-    public static final String KEY_NAME = "name";
-    public static final String KEY_OUT = "out";
-    public static final String KEY_FILTER = "filter";
-    public static final String KEY_SKIN = "skin";
-    public static final String KEY_SDCARD = "sdcard";
-
-    public final static String ACTION_LIST = "list";
-    public final static String ACTION_NEW_VM = ARG_VM;
-    public final static String ACTION_NEW_PROJECT = "project";
-    public final static String ACTION_UPDATE_PROJECT = "update";
+            { VERB_CREATE,
+                OBJECT_AVD,
+                "Creates a new Android Virtual Device." },
+            { VERB_RENAME,
+                OBJECT_AVD,
+                "Renames a new Android Virtual Device." },
+            { VERB_MOVE,
+                OBJECT_AVD,
+                "Moves a new Android Virtual Device." },
+            { VERB_DELETE,
+                OBJECT_AVD,
+                "Deletes a new Android Virtual Device." },
     
-    private final static String[][] ACTIONS = {
-        { ACTION_LIST,
-            "Lists existing targets or VMs." },
-        { ACTION_NEW_VM,
-            "Creates a new VM." },
-        { ACTION_NEW_PROJECT,
-            "Creates a new project using a template." },
-        { ACTION_UPDATE_PROJECT,
-            "Updates a project from existing source (must have an AndroidManifest.xml)." },
+            { VERB_CREATE,
+                OBJECT_PROJECT,
+                "Creates a new Android Project." },
+            { VERB_UPDATE,
+                OBJECT_PROJECT,
+                "Updates an Android Project (must have an AndroidManifest.xml)." },
         };
     
     public SdkCommandLine(ISdkLog logger) {
         super(logger, ACTIONS);
 
-        define(MODE.ENUM, false, ACTION_LIST, "f", KEY_FILTER,
-                "List filter", new String[] { ARG_ALL, ARG_TARGET, ARG_VM });
-
-        define(MODE.STRING, false, ACTION_NEW_VM, "o", KEY_OUT,
-                "Location path of new VM", null);
-        define(MODE.STRING, true, ACTION_NEW_VM, "n", KEY_NAME,
-                "Name of the new VM", null);
-        define(MODE.INTEGER, true, ACTION_NEW_VM, "t", KEY_TARGET_ID,
-                "Target id of the new VM", null);
-        define(MODE.STRING, true, ACTION_NEW_VM, "s", KEY_SKIN,
-                "Skin of the new VM", null);
-        define(MODE.STRING, false, ACTION_NEW_VM, "c", KEY_SDCARD,
-                "Path to a shared SD card image, or size of a new sdcard for the new VM", null);
-
-        define(MODE.ENUM, true, ACTION_NEW_PROJECT, "m", KEY_MODE,
+        define(MODE.STRING, false, 
+                VERB_CREATE, OBJECT_AVD,
+                "p", KEY_PATH,
+                "Location path of the parent directory where the new AVD will be created", null);
+        define(MODE.STRING, true, 
+                VERB_CREATE, OBJECT_AVD,
+                "n", KEY_NAME,
+                "Name of the new AVD", null);
+        define(MODE.INTEGER, true, 
+                VERB_CREATE, OBJECT_AVD,
+                "t", KEY_TARGET_ID,
+                "Target id of the new AVD", null);
+        define(MODE.STRING, true, 
+                VERB_CREATE, OBJECT_AVD,
+                "s", KEY_SKIN,
+                "Skin of the new AVD", null);
+        define(MODE.STRING, false, 
+                VERB_CREATE, OBJECT_AVD,
+                "c", KEY_SDCARD,
+                "Path to a shared SD card image, or size of a new sdcard for the new AVD", null);
+        define(MODE.BOOLEAN, false, 
+                VERB_CREATE, OBJECT_AVD,
+                "f", KEY_FORCE,
+                "Force creation (override an existing AVD)", false);
+
+        define(MODE.ENUM, true, 
+                VERB_CREATE, OBJECT_PROJECT,
+                "m", KEY_MODE,
                 "Project mode", new String[] { ARG_ACTIVITY, ARG_ALIAS });
-        define(MODE.STRING, true, ACTION_NEW_PROJECT, "o", KEY_OUT,
+        define(MODE.STRING, true, 
+                VERB_CREATE, OBJECT_PROJECT,
+                "p", KEY_PATH,
                 "Location path of new project", null);
-        define(MODE.INTEGER, true, ACTION_NEW_PROJECT, "t", KEY_TARGET_ID,
+        define(MODE.INTEGER, true, 
+                VERB_CREATE, OBJECT_PROJECT,
+                "t", KEY_TARGET_ID,
                 "Target id of the new project", null);
-        define(MODE.STRING, true, ACTION_NEW_PROJECT, "p", KEY_PACKAGE,
+        define(MODE.STRING, true, 
+                VERB_CREATE, OBJECT_PROJECT,
+                "k", KEY_PACKAGE,
                 "Package name", null);
-        define(MODE.STRING, true, ACTION_NEW_PROJECT, "a", KEY_ACTIVITY,
+        define(MODE.STRING, true, 
+                VERB_CREATE, OBJECT_PROJECT,
+                "a", KEY_ACTIVITY,
                 "Activity name", null);
-        define(MODE.STRING, false, ACTION_NEW_PROJECT, "n", KEY_NAME,
+        define(MODE.STRING, false, 
+                VERB_CREATE, OBJECT_PROJECT,
+                "n", KEY_NAME,
                 "Project name", null);
 
-        define(MODE.STRING, true, ACTION_UPDATE_PROJECT, "o", KEY_OUT,
+        define(MODE.STRING, true, 
+                VERB_UPDATE, OBJECT_PROJECT,
+                "p", KEY_PATH,
                 "Location path of the project", null);
-        define(MODE.INTEGER, true, ACTION_UPDATE_PROJECT, "t", KEY_TARGET_ID,
+        define(MODE.INTEGER, true, 
+                VERB_UPDATE, OBJECT_PROJECT,
+                "t", KEY_TARGET_ID,
                 "Target id to set for the project", -1);
-        define(MODE.STRING, false, ACTION_UPDATE_PROJECT, "n", KEY_NAME,
+        define(MODE.STRING, false, 
+                VERB_UPDATE, OBJECT_PROJECT,
+                "n", KEY_NAME,
                 "Project name", null);
     }
     
-    // -- some helpers for list action flags
-    
-    /** Helper to retrieve the --filter for the list action. */
-    public String getListFilter() {
-        return ((String) getValue(ACTION_LIST, KEY_FILTER));
-    }
-
-    // -- some helpers for vm action flags
+    // -- some helpers for AVD action flags
     
-    /** Helper to retrieve the --out location for the new vm action. */
-    public String getNewVmLocation() {
-        return ((String) getValue(ACTION_NEW_VM, KEY_OUT));
+    /** Helper to retrieve the --out location for the new AVD action. */
+    public String getCreateAvdLocation() {
+        return ((String) getValue(VERB_CREATE, OBJECT_AVD, KEY_PATH));
     }
     
-    /** Helper to retrieve the --target id for the new vm action. */
-    public int getNewVmTargetId() {
-        return ((Integer) getValue(ACTION_NEW_VM, KEY_TARGET_ID)).intValue();
+    /** Helper to retrieve the --target id for the new AVD action. */
+    public int getCreateAvdTargetId() {
+        return ((Integer) getValue(VERB_CREATE, OBJECT_AVD, KEY_TARGET_ID)).intValue();
     }
 
-    /** Helper to retrieve the --name for the new vm action. */
-    public String getNewVmName() {
-        return ((String) getValue(ACTION_NEW_VM, KEY_NAME));
+    /** Helper to retrieve the --name for the new AVD action. */
+    public String getCreateAvdName() {
+        return ((String) getValue(VERB_CREATE, OBJECT_AVD, KEY_NAME));
     }
     
-    /** Helper to retrieve the --skin name for the new vm action. */
-    public String getNewVmSkin() {
-        return ((String) getValue(ACTION_NEW_VM, KEY_SKIN));
+    /** Helper to retrieve the --skin name for the new AVD action. */
+    public String getCreateAvdSkin() {
+        return ((String) getValue(VERB_CREATE, OBJECT_AVD, KEY_SKIN));
     }
 
-    /** Helper to retrieve the --sdcard data for the new vm action. */
-    public String getNewVmSdCard() {
-        return ((String) getValue(ACTION_NEW_VM, KEY_SDCARD));
+    /** Helper to retrieve the --sdcard data for the new AVD action. */
+    public String getCreateAvdSdCard() {
+        return ((String) getValue(VERB_CREATE, OBJECT_AVD, KEY_SDCARD));
+    }
+    
+    public boolean getCreateAvdForce() {
+        return ((Boolean) getValue(VERB_CREATE, OBJECT_AVD, KEY_FORCE)).booleanValue();
     }
 
 
     // -- some helpers for project action flags
     
     /** Helper to retrieve the --out location for the new project action. */
-    public String getNewProjectLocation() {
-        return ((String) getValue(ACTION_NEW_PROJECT, KEY_OUT));
+    public String getCreateProjectLocation() {
+        return ((String) getValue(VERB_CREATE, OBJECT_PROJECT, KEY_PATH));
     }
     
     /** Helper to retrieve the --target id for the new project action. */
-    public int getNewProjectTargetId() {
-        return ((Integer) getValue(ACTION_NEW_PROJECT, KEY_TARGET_ID)).intValue();
+    public int getCreateProjectTargetId() {
+        return ((Integer) getValue(VERB_CREATE, OBJECT_PROJECT, KEY_TARGET_ID)).intValue();
     }
 
     /** Helper to retrieve the --name for the new project action. */
-    public String getNewProjectName() {
-        return ((String) getValue(ACTION_NEW_PROJECT, KEY_NAME));
+    public String getCreateProjectName() {
+        return ((String) getValue(VERB_CREATE, OBJECT_PROJECT, KEY_NAME));
     }
 
     /** Helper to retrieve the --package for the new project action. */
-    public String getNewProjectPackage() {
-        return ((String) getValue(ACTION_NEW_PROJECT, KEY_PACKAGE));
+    public String getCreateProjectPackage() {
+        return ((String) getValue(VERB_CREATE, OBJECT_PROJECT, KEY_PACKAGE));
     }
 
     /** Helper to retrieve the --activity for the new project action. */
-    public String getNewProjectActivity() {
-        return ((String) getValue(ACTION_NEW_PROJECT, KEY_ACTIVITY));
+    public String getCreateProjectActivity() {
+        return ((String) getValue(VERB_CREATE, OBJECT_PROJECT, KEY_ACTIVITY));
     }
 
     // -- some helpers for update action flags
     
     /** Helper to retrieve the --out location for the update project action. */
     public String getUpdateProjectLocation() {
-        return ((String) getValue(ACTION_UPDATE_PROJECT, KEY_OUT));
+        return ((String) getValue(VERB_UPDATE, OBJECT_PROJECT, KEY_PATH));
     }
     
     /** Helper to retrieve the --target id for the update project action. */
     public int getUpdateProjectTargetId() {
-        return ((Integer) getValue(ACTION_UPDATE_PROJECT, KEY_TARGET_ID)).intValue();
+        return ((Integer) getValue(VERB_UPDATE, OBJECT_PROJECT, KEY_TARGET_ID)).intValue();
     }
 
     /** Helper to retrieve the --name for the update project action. */
     public String getUpdateProjectName() {
-        return ((String) getValue(ACTION_UPDATE_PROJECT, KEY_NAME));
+        return ((String) getValue(VERB_UPDATE, OBJECT_PROJECT, KEY_NAME));
     }
 }
index 1a82151..918591b 100644 (file)
@@ -38,20 +38,20 @@ public class CommandLineProcessorTest extends TestCase {
         public MockCommandLineProcessor(ISdkLog logger) {
             super(logger,
                   new String[][] {
-                    { "action1", "Some action" },
-                    { "action2", "Another action" },
+                    { "verb1", "action1", "Some action" },
+                    { "verb1", "action2", "Another action" },
             });
             define(MODE.STRING, false /*mandatory*/,
-                    "action1", "1", "first", "non-mandatory flag", null);
+                    "verb1", "action1", "1", "first", "non-mandatory flag", null);
             define(MODE.STRING, true /*mandatory*/,
-                    "action1", "2", "second", "mandatory flag", null);
+                    "verb1", "action1", "2", "second", "mandatory flag", null);
         }
         
         @Override
-        public void printHelpAndExitForAction(String actionFilter,
+        public void printHelpAndExitForAction(String verb, String directObject,
                 String errorFormat, Object... args) {
             mHelpCalled = true;
-            super.printHelpAndExitForAction(actionFilter, errorFormat, args);
+            super.printHelpAndExitForAction(verb, directObject, errorFormat, args);
         }
         
         @Override
@@ -132,14 +132,14 @@ public class CommandLineProcessorTest extends TestCase {
         assertTrue(c.isVerbose());
         assertTrue(c.wasExitCalled());
         assertTrue(c.wasHelpCalled());
-        assertTrue(c.getStdErr().indexOf("Missing action name.") != -1);
+        assertTrue(c.getStdErr().indexOf("Missing verb name.") != -1);
 
         c = new MockCommandLineProcessor(mLog);        
         c.parseArgs(new String[] { "--verbose" });
         assertTrue(c.isVerbose());
         assertTrue(c.wasExitCalled());
         assertTrue(c.wasHelpCalled());
-        assertTrue(c.getStdErr().indexOf("Missing action name.") != -1);
+        assertTrue(c.getStdErr().indexOf("Missing verb name.") != -1);
     }
     
     public final void testHelp() {
@@ -148,39 +148,39 @@ public class CommandLineProcessorTest extends TestCase {
         c.parseArgs(new String[] { "-h" });
         assertTrue(c.wasExitCalled());
         assertTrue(c.wasHelpCalled());
-        assertTrue(c.getStdErr().indexOf("Missing action name.") == -1);
+        assertTrue(c.getStdErr().indexOf("Missing verb name.") == -1);
 
         c = new MockCommandLineProcessor(mLog);        
         c.parseArgs(new String[] { "--help" });
         assertTrue(c.wasExitCalled());
         assertTrue(c.wasHelpCalled());
-        assertTrue(c.getStdErr().indexOf("Missing action name.") == -1);
+        assertTrue(c.getStdErr().indexOf("Missing verb name.") == -1);
     }
 
     public final void testMandatory() {
         MockCommandLineProcessor c = new MockCommandLineProcessor(mLog);        
 
-        c.parseArgs(new String[] { "action1", "-1", "value1", "-2", "value2" });
+        c.parseArgs(new String[] { "verb1", "action1", "-1", "value1", "-2", "value2" });
         assertFalse(c.wasExitCalled());
         assertFalse(c.wasHelpCalled());
         assertEquals("", c.getStdErr());
-        assertEquals("value1", c.getValue("action1", "first"));
-        assertEquals("value2", c.getValue("action1", "second"));
+        assertEquals("value1", c.getValue("verb1", "action1", "first"));
+        assertEquals("value2", c.getValue("verb1", "action1", "second"));
 
         c = new MockCommandLineProcessor(mLog);        
-        c.parseArgs(new String[] { "action1", "-2", "value2" });
+        c.parseArgs(new String[] { "verb1", "action1", "-2", "value2" });
         assertFalse(c.wasExitCalled());
         assertFalse(c.wasHelpCalled());
         assertEquals("", c.getStdErr());
-        assertEquals(null, c.getValue("action1", "first"));
-        assertEquals("value2", c.getValue("action1", "second"));
+        assertEquals(null, c.getValue("verb1", "action1", "first"));
+        assertEquals("value2", c.getValue("verb1", "action1", "second"));
 
         c = new MockCommandLineProcessor(mLog);        
-        c.parseArgs(new String[] { "action1" });
+        c.parseArgs(new String[] { "verb1", "action1" });
         assertTrue(c.wasExitCalled());
         assertTrue(c.wasHelpCalled());
         assertTrue(c.getStdErr().indexOf("must be defined") != -1);
-        assertEquals(null, c.getValue("action1", "first"));
-        assertEquals(null, c.getValue("action1", "second"));
+        assertEquals(null, c.getValue("verb1", "action1", "first"));
+        assertEquals(null, c.getValue("verb1", "action1", "second"));
     }
 }
index 5a2c8e1..07a32e0 100644 (file)
@@ -37,10 +37,10 @@ public class SdkCommandLineTest extends TestCase {
         }
 
         @Override
-        public void printHelpAndExitForAction(String actionFilter,
+        public void printHelpAndExitForAction(String verb, String directObject,
                 String errorFormat, Object... args) {
             mHelpCalled = true;
-            super.printHelpAndExitForAction(actionFilter, errorFormat, args);
+            super.printHelpAndExitForAction(verb, directObject, errorFormat, args);
         }
 
         @Override
@@ -78,34 +78,64 @@ public class SdkCommandLineTest extends TestCase {
         super.tearDown();
     }
 
-    /** Test list with long name and verbose */
-    public final void testList_Long_Verbose() {
+    /** Test list */
+    public final void testList_Avd_Verbose() {
         MockSdkCommandLine c = new MockSdkCommandLine(mLog);
-        assertEquals("all", c.getListFilter());
-        c.parseArgs(new String[] { "-v", "list", "--filter", "vm" });
+        c.parseArgs(new String[] { "-v", "list", "avd" });
         assertFalse(c.wasHelpCalled());
         assertFalse(c.wasExitCalled());
-        assertEquals("vm", c.getListFilter());
+        assertEquals("list", c.getVerb());
+        assertEquals("avd", c.getDirectObject());
         assertTrue(c.isVerbose());
     }
 
-    /** Test list with short name and no verbose */
-    public final void testList_Short() {
+    public final void testList_Target() {
         MockSdkCommandLine c = new MockSdkCommandLine(mLog);
-        assertEquals("all", c.getListFilter());
-        c.parseArgs(new String[] { "list", "-f", "vm" });
+        c.parseArgs(new String[] { "list", "target" });
         assertFalse(c.wasHelpCalled());
         assertFalse(c.wasExitCalled());
-        assertEquals("vm", c.getListFilter());
+        assertEquals("list", c.getVerb());
+        assertEquals("target", c.getDirectObject());
+        assertFalse(c.isVerbose());
     }
-    
-    /** Test list with long name and missing parameter */
-    public final void testList_Long_MissingParam() {
+
+    public final void testList_None() {
+        MockSdkCommandLine c = new MockSdkCommandLine(mLog);
+        c.parseArgs(new String[] { "list" });
+        assertFalse(c.wasHelpCalled());
+        assertFalse(c.wasExitCalled());
+        assertEquals("list", c.getVerb());
+        assertEquals("", c.getDirectObject());
+        assertFalse(c.isVerbose());
+    }
+
+    public final void testList_Invalid() {
         MockSdkCommandLine c = new MockSdkCommandLine(mLog);
-        assertEquals("all", c.getListFilter());
-        c.parseArgs(new String[] { "list", "--filter" });
+        c.parseArgs(new String[] { "list", "unknown" });
         assertTrue(c.wasHelpCalled());
         assertTrue(c.wasExitCalled());
-        assertEquals("all", c.getListFilter());
+        assertEquals(null, c.getVerb());
+        assertEquals(null, c.getDirectObject());
+        assertFalse(c.isVerbose());
+    }
+
+    public final void testList_Plural() {
+        MockSdkCommandLine c = new MockSdkCommandLine(mLog);
+        c.parseArgs(new String[] { "list", "avds" });
+        assertFalse(c.wasHelpCalled());
+        assertFalse(c.wasExitCalled());
+        assertEquals("list", c.getVerb());
+        // we get the non-plural form
+        assertEquals("avd", c.getDirectObject());
+        assertFalse(c.isVerbose());
+
+        c = new MockSdkCommandLine(mLog);
+        c.parseArgs(new String[] { "list", "targets" });
+        assertFalse(c.wasHelpCalled());
+        assertFalse(c.wasExitCalled());
+        assertEquals("list", c.getVerb());
+        // we get the non-plural form
+        assertEquals("target", c.getDirectObject());
+        assertFalse(c.isVerbose());
     }
 }
index 8cbe44a..4894517 100644 (file)
 
 package com.android.sdklib;
 
+import java.util.Formatter;
+
 /**
  * Interface used to display warnings/errors while parsing the SDK content.
  */
 public interface ISdkLog {
+    
+    /**
+     * Prints a warning message on stdout.
+     * <p/>
+     * Implementations should only display warnings in verbose mode.
+     * The message should be prefixed with "Warning:".
+     * 
+     * @param warningFormat is an optional error format. If non-null, it will be printed
+     *          using a {@link Formatter} with the provided arguments.
+     * @param args provides the arguments for warningFormat.
+     */
     void warning(String warningFormat, Object... args);
+    
+    /**
+     * Prints an error message on stderr.
+     * <p/>
+     * Implementation should always display errors, independent of verbose mode.
+     * The message should be prefixed with "Error:".
+     * 
+     * @param t is an optional {@link Throwable} or {@link Exception}. If non-null, it's
+     *          message will be printed out.
+     * @param errorFormat is an optional error format. If non-null, it will be printed
+     *          using a {@link Formatter} with the provided arguments.
+     * @param args provides the arguments for errorFormat.
+     */
     void error(Throwable t, String errorFormat, Object... args);
+    
+    /**
+     * Prints a message as-is on stdout.
+     * <p/>
+     * Implementation should always display errors, independent of verbose mode.
+     * No prefix is used, the message is printed as-is after formatting.
+     * 
+     * @param msgFormat is an optional error format. If non-null, it will be printed
+     *          using a {@link Formatter} with the provided arguments.
+     * @param args provides the arguments for msgFormat.
+     */
     void printf(String msgFormat, Object... args);
 }
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.sdklib.vm;
+package com.android.sdklib.avd;
 
 import com.android.prefs.AndroidLocation;
 import com.android.prefs.AndroidLocation.AndroidLocationException;
@@ -39,12 +39,13 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 /**
- * Virtual Machine manager to access the list of VMs or create new ones.
+ * Virtual Device Manager to access the list of AVDs or create new ones.
  */
-public final class VmManager {
+public final class AvdManager {
     
-    private final static String VM_INFO_PATH = "path";
-    private final static String VM_INFO_TARGET = "target";
+    private static final String AVD_FOLDER_EXTENSION = ".avd";
+    private final static String AVD_INFO_PATH = "path";
+    private final static String AVD_INFO_TARGET = "target";
 
     private final static String IMAGE_USERDATA = "userdata.img";
     private final static String CONFIG_INI = "config.ini";
@@ -54,7 +55,7 @@ public final class VmManager {
 
     private final static Pattern SDCARD_SIZE_PATTERN = Pattern.compile("\\d+[MK]?");
 
-    public static final class VmInfo {
+    public static final class AvdInfo {
         String name;
         String path;
         IAndroidTarget target;
@@ -72,30 +73,30 @@ public final class VmManager {
         }
     }
 
-    private final ArrayList<VmInfo> mVmList = new ArrayList<VmInfo>();
+    private final ArrayList<AvdInfo> mAvdList = new ArrayList<AvdInfo>();
     private ISdkLog mSdkLog;
     private final SdkManager mSdk;
 
-    public VmManager(SdkManager sdk, ISdkLog sdkLog) throws AndroidLocationException {
+    public AvdManager(SdkManager sdk, ISdkLog sdkLog) throws AndroidLocationException {
         mSdk = sdk;
         mSdkLog = sdkLog;
-        buildVmList();
+        buildAvdList();
     }
 
     /**
-     * Returns the existing VMs.
-     * @return a newly allocated arrays containing all the VMs.
+     * Returns the existing AVDs.
+     * @return a newly allocated array containing all the AVDs.
      */
-    public VmInfo[] getVms() {
-        return mVmList.toArray(new VmInfo[mVmList.size()]);
+    public AvdInfo[] getAvds() {
+        return mAvdList.toArray(new AvdInfo[mAvdList.size()]);
     }
 
     /**
-     * Returns the {@link VmInfo} matching the given <var>name</var>.
-     * @return the matching VmInfo or <code>null</code> if none were found.
+     * Returns the {@link AvdInfo} matching the given <var>name</var>.
+     * @return the matching AvdInfo or <code>null</code> if none were found.
      */
-    public VmInfo getVm(String name) {
-        for (VmInfo info : mVmList) {
+    public AvdInfo getAvd(String name) {
+        for (AvdInfo info : mAvdList) {
             if (info.name.equals(name)) {
                 return info;
             }
@@ -105,19 +106,20 @@ public final class VmManager {
     }
 
     /**
-     * Creates a new VM. It is expected that there is no existing VM with this name already.
-     * @param parentFolder the folder to contain the VM. A new folder will be created in this
-     * folder with the name of the VM
-     * @param name the name of the VM
-     * @param target the target of the VM
+     * Creates a new AVD. It is expected that there is no existing AVD with this name already.
+     * @param parentFolder the folder to contain the AVD. A new folder will be created in this
+     * folder with the name of the AVD
+     * @param name the name of the AVD
+     * @param target the target of the AVD
      * @param skinName the name of the skin. Can be null.
      * @param sdcard the parameter value for the sdCard. Can be null. This is either a path to
      * an existing sdcard image or a sdcard size (\d+, \d+K, \dM).
-     * @param hardwareConfig the hardware setup for the VM
+     * @param hardwareConfig the hardware setup for the AVD
+     * @param removePrevious If true remove any previous files.
      */
-    public VmInfo createVm(String parentFolder, String name, IAndroidTarget target,
+    public AvdInfo createAvd(String parentFolder, String name, IAndroidTarget target,
             String skinName, String sdcard, Map<String,String> hardwareConfig,
-            ISdkLog log) {
+            boolean removePrevious, ISdkLog log) {
         
         try {
             File rootDirectory = new File(parentFolder);
@@ -128,24 +130,31 @@ public final class VmManager {
                 return null;
             }
             
-            File vmFolder = new File(parentFolder, name + ".avm");
-            if (vmFolder.exists()) {
-                if (log != null) {
-                    log.error(null, "Folder %s is in the way.", vmFolder.getAbsolutePath());
+            File avdFolder = new File(parentFolder, name + AVD_FOLDER_EXTENSION);
+            if (avdFolder.exists()) {
+                if (removePrevious) {
+                    // AVD already exists and removePrevious is set, try to remove the
+                    // directory's content first (but not the directory itself).
+                    recursiveDelete(avdFolder);
+                } else {
+                    // AVD shouldn't already exist if removePrevious is false.
+                    if (log != null) {
+                        log.error(null, "Folder %s is in the way.", avdFolder.getAbsolutePath());
+                    }
+                    return null;
                 }
-                return null;
             }
 
-            // create the vm folder.
-            vmFolder.mkdir();
+            // create the AVD folder.
+            avdFolder.mkdir();
 
             HashMap<String, String> values = new HashMap<String, String>();
 
             // prepare the ini file.
-            String vmRoot = AndroidLocation.getFolder() + AndroidLocation.FOLDER_VMS;
-            File iniFile = new File(vmRoot, name + ".ini");
-            values.put(VM_INFO_PATH, vmFolder.getAbsolutePath());
-            values.put(VM_INFO_TARGET, target.hashString());
+            String avdRoot = AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD;
+            File iniFile = new File(avdRoot, name + ".ini");
+            values.put(AVD_INFO_PATH, avdFolder.getAbsolutePath());
+            values.put(AVD_INFO_TARGET, target.hashString());
             createConfigIni(iniFile, values);
 
             // writes the userdata.img in it.
@@ -153,7 +162,7 @@ public final class VmManager {
             File userdataSrc = new File(imagePath, IMAGE_USERDATA);
             FileInputStream fis = new FileInputStream(userdataSrc);
             
-            File userdataDest = new File(vmFolder, IMAGE_USERDATA);
+            File userdataDest = new File(avdFolder, IMAGE_USERDATA);
             FileOutputStream fos = new FileOutputStream(userdataDest);
             
             byte[] buffer = new byte[4096];
@@ -193,7 +202,7 @@ public final class VmManager {
                     Matcher m = SDCARD_SIZE_PATTERN.matcher(sdcard);
                     if (m.matches()) {
                         // create the sdcard.
-                        sdcardFile = new File(vmFolder, "sdcard.img");
+                        sdcardFile = new File(avdFolder, "sdcard.img");
                         String path = sdcardFile.getAbsolutePath();
                         
                         // execute mksdcard with the proper parameters.
@@ -224,28 +233,27 @@ public final class VmManager {
                 values.putAll(hardwareConfig);
             }
 
-            File configIniFile = new File(vmFolder, CONFIG_INI);
+            File configIniFile = new File(avdFolder, CONFIG_INI);
             createConfigIni(configIniFile, values);
             
             if (log != null) {
                 if (target.isPlatform()) {
-                    log.printf("Created VM '%s' based on %s\n", name, target.getName());
+                    log.printf("Created AVD '%s' based on %s\n", name, target.getName());
                 } else {
-                    log.printf(
-                            "Created VM '%s' based on %s (%s)\n", name, target.getName(),
-                            target.getVendor());
+                    log.printf("Created AVD '%s' based on %s (%s)\n", name, target.getName(),
+                               target.getVendor());
                 }
             }
             
-            // create the VmInfo object, and add it to the list
-            VmInfo vmInfo = new VmInfo();
-            vmInfo.name = name;
-            vmInfo.path = vmFolder.getAbsolutePath();
-            vmInfo.target = target;
+            // create the AvdInfo object, and add it to the list
+            AvdInfo avdInfo = new AvdInfo();
+            avdInfo.name = name;
+            avdInfo.path = avdFolder.getAbsolutePath();
+            avdInfo.target = target;
             
-            mVmList.add(vmInfo);
+            mAvdList.add(avdInfo);
             
-            return vmInfo;
+            return avdInfo;
         } catch (AndroidLocationException e) {
             if (log != null) {
                 log.error(e, null);
@@ -259,21 +267,35 @@ public final class VmManager {
         return null;
     }
 
-    private void buildVmList() throws AndroidLocationException {
+    /**
+     * Helper method to recursively delete a folder's content (but not the folder itself).
+     * 
+     * @throws SecurityException like {@link File#delete()} does if file/folder is not writable.
+     */
+    public void recursiveDelete(File folder) {
+        for (File f : folder.listFiles()) {
+            if (f.isDirectory()) {
+                recursiveDelete(folder);
+            }
+            f.delete();
+        }
+    }
+
+    private void buildAvdList() throws AndroidLocationException {
         // get the Android prefs location.
-        String vmRoot = AndroidLocation.getFolder() + AndroidLocation.FOLDER_VMS;
+        String avdRoot = AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD;
         
         // ensure folder validity.
-        File folder = new File(vmRoot);
+        File folder = new File(avdRoot);
         if (folder.isFile()) {
-            throw new AndroidLocationException(String.format("%s is not a valid folder.", vmRoot));
+            throw new AndroidLocationException(String.format("%s is not a valid folder.", avdRoot));
         } else if (folder.exists() == false) {
             // folder is not there, we create it and return
             folder.mkdirs();
             return;
         }
         
-        File[] vms = folder.listFiles(new FilenameFilter() {
+        File[] avds = folder.listFiles(new FilenameFilter() {
             public boolean accept(File parent, String name) {
                 if (INI_NAME_PATTERN.matcher(name).matches()) {
                     // check it's a file and not a folder
@@ -284,23 +306,23 @@ public final class VmManager {
             }
         });
         
-        for (File vm : vms) {
-            VmInfo info = parseVmInfo(vm);
+        for (File avd : avds) {
+            AvdInfo info = parseAvdInfo(avd);
             if (info != null) {
-                mVmList.add(info);
+                mAvdList.add(info);
             }
         }
     }
     
-    private VmInfo parseVmInfo(File path) {
+    private AvdInfo parseAvdInfo(File path) {
         Map<String, String> map = SdkManager.parsePropertyFile(path, mSdkLog);
         
-        String vmPath = map.get(VM_INFO_PATH);
-        if (vmPath == null) {
+        String avdPath = map.get(AVD_INFO_PATH);
+        if (avdPath == null) {
             return null;
         }
         
-        String targetHash = map.get(VM_INFO_TARGET);
+        String targetHash = map.get(AVD_INFO_TARGET);
         if (targetHash == null) {
             return null;
         }
@@ -310,14 +332,14 @@ public final class VmManager {
             return null;
         }
 
-        VmInfo info = new VmInfo();
+        AvdInfo info = new AvdInfo();
         Matcher matcher = INI_NAME_PATTERN.matcher(path.getName());
         if (matcher.matches()) {
             info.name = matcher.group(1);
         } else {
             info.name = path.getName(); // really this should not happen.
         }
-        info.path = vmPath;
+        info.path = avdPath;
         info.target = target;
         
         return info;
@@ -447,4 +469,14 @@ public final class VmManager {
         return process.waitFor();
     }
 
+    /**
+     * Removes an {@link AvdInfo} from the internal list.
+     * 
+     * @param avdInfo The {@link AvdInfo} to remove.
+     * @return true if this {@link AvdInfo} was present and has been removed.
+     */
+    public boolean removeAvd(AvdInfo avdInfo) {
+        return mAvdList.remove(avdInfo);
+    }
+
 }
@@ -17,7 +17,7 @@
 package com.android.sdkuilib;
 
 import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.vm.VmManager.VmInfo;
+import com.android.sdklib.avd.AvdManager.AvdInfo;
 
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.ControlAdapter;
@@ -40,16 +40,16 @@ import java.util.ArrayList;
 
 
 /**
- * The VM selector is a table that is added to the given parent composite.
+ * The AVD selector is a table that is added to the given parent composite.
  * <p/>
- * To use, create it using {@link #VmSelector(Composite, VmInfo[], boolean)} then
- * call {@link #setSelection(VmInfo)}, {@link #setSelectionListener(SelectionListener)}
+ * To use, create it using {@link #AvdSelector(Composite, AvdInfo[], boolean)} then
+ * call {@link #setSelection(AvdInfo)}, {@link #setSelectionListener(SelectionListener)}
  * and finally use {@link #getFirstSelected()} or {@link #getAllSelected()} to retrieve the
  * selection.
  */
-public final class VmSelector {
+public final class AvdSelector {
     
-    private VmInfo[] mVms;
+    private AvdInfo[] mAvds;
     private final boolean mAllowMultipleSelection;
     private SelectionListener mSelectionListener;
     private Table mTable;
@@ -59,12 +59,12 @@ public final class VmSelector {
      * Creates a new SDK Target Selector.
      * 
      * @param parent The parent composite where the selector will be added.
-     * @param vms The list of vms. This is <em>not</em> copied, the caller must not modify.
+     * @param avds The list of AVDs. This is <em>not</em> copied, the caller must not modify.
      * @param allowMultipleSelection True if more than one SDK target can be selected at the same
      *        time.
      */
-    public VmSelector(Composite parent, VmInfo[] vms, boolean allowMultipleSelection) {
-        mVms = vms;
+    public AvdSelector(Composite parent, AvdInfo[] avds, boolean allowMultipleSelection) {
+        mAvds = avds;
 
         // Layout has 1 column
         Composite group = new Composite(parent, SWT.NONE);
@@ -89,7 +89,7 @@ public final class VmSelector {
 
         // create the table columns
         final TableColumn column0 = new TableColumn(mTable, SWT.NONE);
-        column0.setText("VM Name");
+        column0.setText("AVD Name");
         final TableColumn column1 = new TableColumn(mTable, SWT.NONE);
         column1.setText("Target Name");
         final TableColumn column2 = new TableColumn(mTable, SWT.NONE);
@@ -104,25 +104,25 @@ public final class VmSelector {
     }
     
     /**
-     * Sets a new set of VM, with an optional filter.
+     * Sets a new set of AVD, with an optional filter.
      * <p/>This must be called from the UI thread.
      * 
-     * @param vms The list of vms. This is <em>not</em> copied, the caller must not modify.
-     * @param filter An IAndroidTarget. If non-null, only VM whose target are compatible with the
+     * @param avds The list of AVDs. This is <em>not</em> copied, the caller must not modify.
+     * @param filter An IAndroidTarget. If non-null, only AVD whose target are compatible with the
      * filter target will displayed an available for selection.
      */
-    public void setVms(VmInfo[] vms, IAndroidTarget filter) {
-        mVms = vms;
+    public void setAvds(AvdInfo[] avds, IAndroidTarget filter) {
+        mAvds = avds;
         fillTable(mTable, filter);
     }
 
     /**
-     * Returns the list of known Vms.
+     * Returns the list of known AVDs.
      * <p/>
      * This is not a copy. Callers must <em>not</em> modify this array.
      */
-    public VmInfo[] getVms() {
-        return mVms;
+    public AvdInfo[] getAvds() {
+        return mAvds;
     }
 
     /**
@@ -151,11 +151,11 @@ public final class VmSelector {
      * @param target the target to be selection
      * @return true if the target could be selected, false otherwise.
      */
-    public boolean setSelection(VmInfo target) {
+    public boolean setSelection(AvdInfo target) {
         boolean found = false;
         boolean modified = false;
         for (TableItem i : mTable.getItems()) {
-            if ((VmInfo) i.getData() == target) {
+            if ((AvdInfo) i.getData() == target) {
                 found = true;
                 if (!i.getChecked()) {
                     modified = true;
@@ -181,14 +181,14 @@ public final class VmSelector {
      * @see #getFirstSelected()
      * @return An array of selected items. The list can be empty but not null.
      */
-    public VmInfo[] getAllSelected() {
+    public AvdInfo[] getAllSelected() {
         ArrayList<IAndroidTarget> list = new ArrayList<IAndroidTarget>();
         for (TableItem i : mTable.getItems()) {
             if (i.getChecked()) {
                 list.add((IAndroidTarget) i.getData());
             }
         }
-        return list.toArray(new VmInfo[list.size()]);
+        return list.toArray(new AvdInfo[list.size()]);
     }
 
     /**
@@ -198,10 +198,10 @@ public final class VmSelector {
      * @see #getAllSelected()
      * @return The first selected item or null.
      */
-    public VmInfo getFirstSelected() {
+    public AvdInfo getFirstSelected() {
         for (TableItem i : mTable.getItems()) {
             if (i.getChecked()) {
-                return (VmInfo) i.getData();
+                return (AvdInfo) i.getData();
             }
         }
         return null;
@@ -283,7 +283,7 @@ public final class VmSelector {
     }
 
     /**
-     * Fills the table with all VM.
+     * Fills the table with all AVD.
      * The table columns are:
      * <ul>
      * <li>column 0: sdk name
@@ -294,14 +294,14 @@ public final class VmSelector {
      */
     private void fillTable(final Table table, IAndroidTarget filter) {
         table.removeAll();
-        if (mVms != null && mVms.length > 0) {
+        if (mAvds != null && mAvds.length > 0) {
             table.setEnabled(true);
-            for (VmInfo vm : mVms) {
-                if (filter == null || filter.isCompatibleBaseFor(vm.getTarget())) {
+            for (AvdInfo avd : mAvds) {
+                if (filter == null || filter.isCompatibleBaseFor(avd.getTarget())) {
                     TableItem item = new TableItem(table, SWT.NONE);
-                    item.setData(vm);
-                    item.setText(0, vm.getName());
-                    IAndroidTarget target = vm.getTarget();
+                    item.setData(avd);
+                    item.setText(0, avd.getName());
+                    IAndroidTarget target = avd.getTarget();
                     item.setText(1, target.getFullName());
                     item.setText(2, target.getApiVersionName());
                     item.setText(3, Integer.toString(target.getApiVersionNumber()));
@@ -314,7 +314,7 @@ public final class VmSelector {
             TableItem item = new TableItem(table, SWT.NONE);
             item.setData(null);
             item.setText(0, "--");
-            item.setText(1, "No VM available");
+            item.setText(1, "No AVD available");
             item.setText(2, "--");
             item.setText(3, "--");
         }
@@ -365,13 +365,13 @@ public final class VmSelector {
     }
 
     /**
-     * Updates the description label with the path of the item's VM, if any.
+     * Updates the description label with the path of the item's AVD, if any.
      */
     private void updateDescription(TableItem item) {
         if (item != null) {
             Object data = item.getData();
-            if (data instanceof VmInfo) {
-                String newTooltip = ((VmInfo) data).getPath();
+            if (data instanceof AvdInfo) {
+                String newTooltip = ((AvdInfo) data).getPath();
                 mDescription.setText(newTooltip == null ? "" : newTooltip);  //$NON-NLS-1$
             }
         }