OSDN Git Service

Merge 74f725db from master: Add snapshot handling for AVD creation, details, and...
authorTim Baverstock <weasel@google.com>
Tue, 30 Nov 2010 11:56:04 +0000 (11:56 +0000)
committerXavier Ducrohet <xav@android.com>
Thu, 13 Jan 2011 01:41:19 +0000 (17:41 -0800)
Also respect 'Wipe User Data' by disabling snapshot launch, and support 'Edit AVD'.

Change-Id: I2fee9361d78d70b80314523e36b48fbd80b03ca7

17 files changed:
androidprefs/src/com/android/prefs/AndroidLocation.java
build/tools.atree
emulator/snapshot/snapshots.img [new file with mode: 0644]
sdkmanager/app/src/com/android/sdkmanager/CommandLineProcessor.java
sdkmanager/app/src/com/android/sdkmanager/Main.java
sdkmanager/app/src/com/android/sdkmanager/SdkCommandLine.java
sdkmanager/app/tests/com/android/sdkmanager/AvdManagerTest.java [new file with mode: 0644]
sdkmanager/app/tests/com/android/sdkmanager/MainTest.java [new file with mode: 0644]
sdkmanager/app/tests/com/android/sdkmanager/MockLog.java [new file with mode: 0644]
sdkmanager/app/tests/com/android/sdkmanager/SdkCommandLineTest.java
sdkmanager/app/tests/com/android/sdkmanager/SdkManagerTestUtil.java [new file with mode: 0644]
sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java
sdkmanager/libs/sdklib/src/com/android/sdklib/internal/avd/AvdManager.java
sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdCreationDialog.java
sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdDetailsDialog.java
sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdSelector.java
sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdStartDialog.java

index 9a537d5..c36048a 100644 (file)
@@ -82,6 +82,13 @@ public final class AndroidLocation {
     }
 
     /**
+     * Resets the folder used to store android related files. For testing.
+     */
+    public final static void resetFolder() {
+        sPrefsLocation = null;
+    }
+
+    /**
      * Checks a list of system properties and/or system environment variables for validity, and
      * existing director, and returns the first one.
      * @param names
index 140a35e..c28f044 100644 (file)
@@ -41,6 +41,7 @@ bin/zipalign     tools/zipalign
 
 # emulator
 bin/emulator     tools/emulator
+sdk/emulator/snapshot/snapshots.img tools/lib/emulator/snapshots.img
 
 # Java-Based SDK Tools
 bin/ddms            tools/ddms
diff --git a/emulator/snapshot/snapshots.img b/emulator/snapshot/snapshots.img
new file mode 100644 (file)
index 0000000..c05bbda
Binary files /dev/null and b/emulator/snapshot/snapshots.img differ
index 73b0f6b..8f5dec4 100644 (file)
@@ -828,6 +828,7 @@ class CommandLineProcessor {
      * Internal helper to define a new argument for a give action.
      *
      * @param mode The {@link Mode} for the argument.
+     * @param mandatory The argument is required (never if {@link Mode.BOOLEAN})
      * @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. Can be empty but not null.
index e951cc5..1fe6d97 100644 (file)
@@ -19,17 +19,17 @@ package com.android.sdkmanager;
 import com.android.prefs.AndroidLocation;
 import com.android.prefs.AndroidLocation.AndroidLocationException;
 import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.IAndroidTarget.IOptionalLibrary;
 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.internal.avd.AvdManager;
-import com.android.sdklib.internal.avd.HardwareProperties;
 import com.android.sdklib.internal.avd.AvdManager.AvdInfo;
+import com.android.sdklib.internal.avd.HardwareProperties;
 import com.android.sdklib.internal.avd.HardwareProperties.HardwareProperty;
 import com.android.sdklib.internal.project.ProjectCreator;
-import com.android.sdklib.internal.project.ProjectProperties;
 import com.android.sdklib.internal.project.ProjectCreator.OutputLevel;
+import com.android.sdklib.internal.project.ProjectProperties;
 import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
 import com.android.sdklib.io.FileWrapper;
 import com.android.sdklib.repository.SdkRepoConstants;
@@ -133,6 +133,11 @@ public class Main {
         };
     }
 
+    /** For testing */
+    public void setLogger(ISdkLog logger) {
+        mSdkLog = logger;
+    }
+
     /**
      * Init the application by making sure the SDK path is available and
      * doing basic parsing of the SDK.
@@ -788,72 +793,84 @@ public class Main {
     }
 
     /**
-     * Displays the list of available AVDs.
+     * Displays the list of available AVDs for the given AvdManager.
+     *
+     * @param avdManager
      */
-    private void displayAvdList() {
-        try {
-            AvdManager avdManager = new AvdManager(mSdkManager, mSdkLog);
+    public void displayAvdList(AvdManager avdManager) {
+        mSdkLog.printf("Available Android Virtual Devices:\n");
+
+        AvdInfo[] avds = avdManager.getValidAvds();
+        for (int index = 0 ; index < avds.length ; index++) {
+            AvdInfo info = avds[index];
+            if (index > 0) {
+                mSdkLog.printf("---------\n");
+            }
+            mSdkLog.printf("    Name: %s\n", info.getName());
+            mSdkLog.printf("    Path: %s\n", info.getPath());
 
-            mSdkLog.printf("Available Android Virtual Devices:\n");
+            // get the target of the AVD
+            IAndroidTarget target = info.getTarget();
+            if (target.isPlatform()) {
+                mSdkLog.printf("  Target: %s (API level %s)\n", target.getName(),
+                        target.getVersion().getApiString());
+            } else {
+                mSdkLog.printf("  Target: %s (%s)\n", target.getName(), target
+                        .getVendor());
+                mSdkLog.printf("          Based on Android %s (API level %s)\n",
+                        target.getVersionName(), target.getVersion().getApiString());
+            }
 
-            AvdInfo[] avds = avdManager.getValidAvds();
-            for (int index = 0 ; index < avds.length ; index++) {
-                AvdInfo info = avds[index];
-                if (index > 0) {
-                    mSdkLog.printf("---------\n");
+            // display some extra values.
+            Map<String, String> properties = info.getProperties();
+            if (properties != null) {
+                String skin = properties.get(AvdManager.AVD_INI_SKIN_NAME);
+                if (skin != null) {
+                    mSdkLog.printf("    Skin: %s\n", skin);
                 }
-                mSdkLog.printf("    Name: %s\n", info.getName());
-                mSdkLog.printf("    Path: %s\n", info.getPath());
-
-                // get the target of the AVD
-                IAndroidTarget target = info.getTarget();
-                if (target.isPlatform()) {
-                    mSdkLog.printf("  Target: %s (API level %s)\n", target.getName(),
-                            target.getVersion().getApiString());
-                } else {
-                    mSdkLog.printf("  Target: %s (%s)\n", target.getName(), target
-                            .getVendor());
-                    mSdkLog.printf("          Based on Android %s (API level %s)\n",
-                            target.getVersionName(), target.getVersion().getApiString());
+                String sdcard = properties.get(AvdManager.AVD_INI_SDCARD_SIZE);
+                if (sdcard == null) {
+                    sdcard = properties.get(AvdManager.AVD_INI_SDCARD_PATH);
                 }
-
-                // display some extra values.
-                Map<String, String> properties = info.getProperties();
-                if (properties != null) {
-                    String skin = properties.get(AvdManager.AVD_INI_SKIN_NAME);
-                    if (skin != null) {
-                        mSdkLog.printf("    Skin: %s\n", skin);
-                    }
-                    String sdcard = properties.get(AvdManager.AVD_INI_SDCARD_SIZE);
-                    if (sdcard == null) {
-                        sdcard = properties.get(AvdManager.AVD_INI_SDCARD_PATH);
-                    }
-                    if (sdcard != null) {
-                        mSdkLog.printf("  Sdcard: %s\n", sdcard);
-                    }
+                if (sdcard != null) {
+                    mSdkLog.printf("  Sdcard: %s\n", sdcard);
+                }
+                String snapshot = properties.get(AvdManager.AVD_INI_SNAPSHOT_PRESENT);
+                if (snapshot != null) {
+                    mSdkLog.printf("Snapshot: %s\n", snapshot);
                 }
             }
+        }
 
-            // Are there some unused AVDs?
-            AvdInfo[] badAvds = avdManager.getBrokenAvds();
+        // Are there some unused AVDs?
+        AvdInfo[] badAvds = avdManager.getBrokenAvds();
 
-            if (badAvds.length == 0) {
-                return;
+        if (badAvds.length == 0) {
+            return;
+        }
+
+        mSdkLog.printf("\nThe following Android Virtual Devices could not be loaded:\n");
+        boolean needSeparator = false;
+        for (AvdInfo info : badAvds) {
+            if (needSeparator) {
+                mSdkLog.printf("---------\n");
             }
+            mSdkLog.printf("    Name: %s\n", info.getName() == null ? "--" : info.getName());
+            mSdkLog.printf("    Path: %s\n", info.getPath() == null ? "--" : info.getPath());
 
-            mSdkLog.printf("\nThe following Android Virtual Devices could not be loaded:\n");
-            boolean needSeparator = false;
-            for (AvdInfo info : badAvds) {
-                if (needSeparator) {
-                    mSdkLog.printf("---------\n");
-                }
-                mSdkLog.printf("    Name: %s\n", info.getName() == null ? "--" : info.getName());
-                mSdkLog.printf("    Path: %s\n", info.getPath() == null ? "--" : info.getPath());
+            String error = info.getErrorMessage();
+            mSdkLog.printf("   Error: %s\n", error == null ? "Uknown error" : error);
+            needSeparator = true;
+        }
+    }
 
-                String error = info.getErrorMessage();
-                mSdkLog.printf("   Error: %s\n", error == null ? "Uknown error" : error);
-                needSeparator = true;
-            }
+    /**
+     * Displays the list of available AVDs.
+     */
+    private void displayAvdList() {
+        try {
+            AvdManager avdManager = new AvdManager(mSdkManager, mSdkLog);
+            displayAvdList(avdManager);
         } catch (AndroidLocationException e) {
             errorAndExit(e.getMessage());
         }
@@ -972,6 +989,7 @@ public class Main {
                     mSdkCommandLine.getParamSdCard(),
                     hardwareConfig,
                     removePrevious,
+                    mSdkCommandLine.getFlagSnapshot(),
                     mSdkLog);
 
         } catch (AndroidLocationException e) {
index 7062fae..5bb6c4e 100644 (file)
@@ -78,6 +78,7 @@ class SdkCommandLine extends CommandLineProcessor {
     public static final String KEY_PROXY_HOST   = "proxy-host";
     public static final String KEY_DRY_MODE     = "dry-mode";
     public static final String KEY_OBSOLETE     = "obsolete";
+    public static final String KEY_SNAPSHOT     = "snapshot";
 
     /**
      * Action definitions for SdkManager command line.
@@ -166,6 +167,9 @@ class SdkCommandLine extends CommandLineProcessor {
         define(Mode.BOOLEAN, false,
                 VERB_CREATE, OBJECT_AVD, "f", KEY_FORCE,
                 "Forces creation (overwrites an existing AVD)", false);
+        define(Mode.BOOLEAN, false,
+                VERB_CREATE, OBJECT_AVD, "a", KEY_SNAPSHOT,
+                "Place a snapshots file in the AVD, to enable persistence.", false);
 
         // --- delete avd ---
 
@@ -408,6 +412,11 @@ class SdkCommandLine extends CommandLineProcessor {
         return ((Boolean) getValue(null, null, KEY_FORCE)).booleanValue();
     }
 
+    /** Helper to retrieve the --snapshot flag. */
+    public boolean getFlagSnapshot() {
+        return ((Boolean) getValue(null, null, KEY_SNAPSHOT)).booleanValue();
+    }
+
     // -- some helpers for avd action flags
 
     /** Helper to retrieve the --rename value for a move verb. */
diff --git a/sdkmanager/app/tests/com/android/sdkmanager/AvdManagerTest.java b/sdkmanager/app/tests/com/android/sdkmanager/AvdManagerTest.java
new file mode 100644 (file)
index 0000000..bb1ba75
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2010 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.sdkmanager;
+
+import static java.io.File.createTempFile;
+
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.SdkManager;
+import com.android.sdklib.internal.avd.AvdManager;
+import com.android.sdklib.internal.project.ProjectProperties;
+import com.android.sdklib.io.FileWrapper;
+
+import java.io.File;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+public class AvdManagerTest extends TestCase {
+
+    private AvdManager mAvdManager;
+    private SdkManager mSdkManager;
+    private MockLog mLog;
+    private File mFakeSdk;
+    private File mAvdFolder;
+    private IAndroidTarget mTarget;
+
+    @Override
+    public void setUp() throws Exception {
+        mLog = new MockLog();
+        mFakeSdk = SdkManagerTestUtil.makeFakeSdk(createTempFile(this.getClass().getSimpleName(), null));
+        mSdkManager = SdkManager.createManager(mFakeSdk.getAbsolutePath(), mLog);
+        assertNotNull("sdkManager location was invalid", mSdkManager);
+
+        mAvdManager = new AvdManager(mSdkManager, mLog);
+        mAvdFolder = new File(mFakeSdk, "avdData");
+        mTarget = mSdkManager.getTargets()[0];
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        SdkManagerTestUtil.deleteDir(mFakeSdk);
+    }
+
+    public void testCreateAvdWithoutSnapshot() {
+        mAvdManager.createAvd(
+                mAvdFolder, this.getName(), mTarget, null, null, null, false, false, mLog);
+
+        assertEquals("[P Created AVD '" + this.getName() + "' based on Android 0.0\n]",
+                mLog.toString());
+        assertTrue("Expected config.ini in " + mAvdFolder,
+                new File(mAvdFolder, "config.ini").exists());
+        Map<String, String> map = ProjectProperties.parsePropertyFile(
+                new FileWrapper(mAvdFolder, "config.ini"), mLog);
+        assertEquals("HVGA", map.get("skin.name"));
+        assertEquals("platforms/v0_0/skins/HVGA", map.get("skin.path"));
+        assertEquals("platforms/v0_0/images/", map.get("image.sysdir.1"));
+        assertEquals(null, map.get("snapshot.present"));
+        assertTrue("Expected userdata.img in " + mAvdFolder,
+                new File(mAvdFolder, "userdata.img").exists());
+        assertFalse("Expected NO snapshots.img in " + mAvdFolder,
+                new File(mAvdFolder, "snapshots.img").exists());
+    }
+
+    public void testCreateAvdWithSnapshot() {
+        mAvdManager.createAvd(
+                mAvdFolder, this.getName(), mTarget, null, null, null, false, true, mLog);
+
+        assertEquals("[P Created AVD '" + this.getName() + "' based on Android 0.0\n]",
+                mLog.toString());
+        assertTrue("Expected snapshots.img in " + mAvdFolder,
+                new File(mAvdFolder, "snapshots.img").exists());
+        Map<String, String> map = ProjectProperties.parsePropertyFile(
+                new FileWrapper(mAvdFolder, "config.ini"), mLog);
+        assertEquals("true", map.get("snapshot.present"));
+    }
+}
diff --git a/sdkmanager/app/tests/com/android/sdkmanager/MainTest.java b/sdkmanager/app/tests/com/android/sdkmanager/MainTest.java
new file mode 100644 (file)
index 0000000..f6e3bbe
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2010 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.sdkmanager;
+
+
+import static java.io.File.createTempFile;
+
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.SdkManager;
+import com.android.sdklib.internal.avd.AvdManager;
+
+import java.io.File;
+
+import junit.framework.TestCase;
+
+public class MainTest extends TestCase {
+
+    private File mFakeSdk;
+    private MockLog mLog;
+    private SdkManager mSdkManager;
+    private AvdManager mAvdManager;
+    private File mAvdFolder;
+    private IAndroidTarget mTarget;
+    private File fakeSdkDir;
+
+    @Override
+    public void setUp() throws Exception {
+        mLog = new MockLog();
+        fakeSdkDir = createTempFile(this.getClass().getSimpleName() + "_" + this.getName(), null);
+        mFakeSdk = SdkManagerTestUtil.makeFakeSdk(fakeSdkDir);
+        mSdkManager = SdkManager.createManager(mFakeSdk.getAbsolutePath(), mLog);
+        assertNotNull("sdkManager location was invalid", mSdkManager);
+
+        mAvdManager = new AvdManager(mSdkManager, mLog);
+        mAvdFolder = new File(mFakeSdk, "avdData");
+        mTarget = mSdkManager.getTargets()[0];
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        SdkManagerTestUtil.deleteDir(mFakeSdk);
+    }
+
+    public void txestDisplayEmptyAvdList() {
+        Main main = new Main();
+        main.setLogger(mLog);
+        mLog.clear();
+        main.displayAvdList(mAvdManager);
+        assertEquals("P Available Android Virtual Devices:\n", mLog.toString());
+    }
+
+    public void testDisplayAvdListOfOneNonSnapshot() {
+        Main main = new Main();
+        main.setLogger(mLog);
+        mAvdManager.createAvd(
+                mAvdFolder, this.getName(), mTarget, null, null, null, false, false, mLog);
+        mLog.clear();
+        main.displayAvdList(mAvdManager);
+        assertEquals(
+                "[P Available Android Virtual Devices:\n"
+                + ", P     Name: " + this.getName() + "\n"
+                + ", P     Path: " + mAvdFolder + "\n"
+                + ", P   Target: Android 0.0 (API level 0)\n"
+                + ", P     Skin: HVGA\n"
+                + "]",
+                mLog.toString());
+    }
+
+    public void testDisplayAvdListOfOneSnapshot() {
+        Main main = new Main();
+        main.setLogger(mLog);
+        mAvdManager.createAvd(
+                mAvdFolder, this.getName(), mTarget, null, null, null, false, true, mLog);
+        mLog.clear();
+        main.displayAvdList(mAvdManager);
+        assertEquals(
+                "[P Available Android Virtual Devices:\n"
+                + ", P     Name: " + this.getName() + "\n"
+                + ", P     Path: " + mAvdFolder + "\n"
+                + ", P   Target: Android 0.0 (API level 0)\n"
+                + ", P     Skin: HVGA\n"
+                + ", P Snapshot: true\n"
+                + "]",
+                mLog.toString());
+    }
+}
diff --git a/sdkmanager/app/tests/com/android/sdkmanager/MockLog.java b/sdkmanager/app/tests/com/android/sdkmanager/MockLog.java
new file mode 100644 (file)
index 0000000..a8ca90a
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * 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.sdkmanager;
+
+import com.android.sdklib.ISdkLog;
+
+import java.util.ArrayList;
+import java.util.Formatter;
+
+public class MockLog implements ISdkLog {
+    private ArrayList<String> mMessages = new ArrayList<String>();
+
+    private void add(String code, String format, Object... args) {
+        mMessages.add(new Formatter().format(code + format, args).toString());
+    }
+
+    @Override
+    public void warning(String format, Object... args) {
+        add("W ", format, args);
+    }
+
+    @Override
+    public void printf(String format, Object... args) {
+        add("P ", format, args);
+    }
+
+    @Override
+    public void error(Throwable t, String format, Object... args) {
+        if (t != null) {
+            add("T", "%s", t.toString());
+        }
+        add("E ", format, args);
+    }
+
+    @Override
+    public String toString() {
+        return mMessages.toString();
+    }
+
+    public void clear() {
+        mMessages.clear();
+    }
+}
index 8206e2a..036d2ec 100644 (file)
@@ -24,7 +24,7 @@ import junit.framework.TestCase;
 public class SdkCommandLineTest extends TestCase {
 
     private StdSdkLog mLog;
-    
+
     /**
      * A mock version of the {@link SdkCommandLine} class that does not
      * exits and discards its stdout/stderr output.
@@ -32,7 +32,7 @@ public class SdkCommandLineTest extends TestCase {
     public static class MockSdkCommandLine extends SdkCommandLine {
         private boolean mExitCalled;
         private boolean mHelpCalled;
-        
+
         public MockSdkCommandLine(ISdkLog logger) {
             super(logger);
         }
@@ -48,12 +48,12 @@ public class SdkCommandLineTest extends TestCase {
         protected void exit() {
             mExitCalled = true;
         }
-        
+
         @Override
         protected void stdout(String format, Object... args) {
             // discard
         }
-        
+
         @Override
         protected void stderr(String format, Object... args) {
             // discard
@@ -62,7 +62,7 @@ public class SdkCommandLineTest extends TestCase {
         public boolean wasExitCalled() {
             return mExitCalled;
         }
-        
+
         public boolean wasHelpCalled() {
             return mHelpCalled;
         }
@@ -139,4 +139,30 @@ public class SdkCommandLineTest extends TestCase {
         assertEquals("target", c.getDirectObject());
         assertFalse(c.isVerbose());
     }
+
+    public final void testCreate_Avd() {
+        MockSdkCommandLine c = new MockSdkCommandLine(mLog);
+        c.parseArgs(new String[] { "create", "avd", "-t", "android-100", "-n", "myProject" });
+        assertFalse(c.wasHelpCalled());
+        assertFalse(c.wasExitCalled());
+        assertEquals("create", c.getVerb());
+        assertEquals("avd", c.getDirectObject());
+        assertFalse(c.getFlagSnapshot());
+        assertEquals("android-100", c.getParamTargetId());
+        assertEquals("myProject", c.getParamName());
+        assertFalse(c.isVerbose());
+    }
+
+    public final void testCreate_Avd_Snapshot() {
+        MockSdkCommandLine c = new MockSdkCommandLine(mLog);
+        c.parseArgs(new String[] { "create", "avd", "-t", "android-100", "-n", "myProject", "-a" });
+        assertFalse(c.wasHelpCalled());
+        assertFalse(c.wasExitCalled());
+        assertEquals("create", c.getVerb());
+        assertEquals("avd", c.getDirectObject());
+        assertTrue(c.getFlagSnapshot());
+        assertEquals("android-100", c.getParamTargetId());
+        assertEquals("myProject", c.getParamName());
+        assertFalse(c.isVerbose());
+    }
 }
diff --git a/sdkmanager/app/tests/com/android/sdkmanager/SdkManagerTestUtil.java b/sdkmanager/app/tests/com/android/sdkmanager/SdkManagerTestUtil.java
new file mode 100644 (file)
index 0000000..96efb5c
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * 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.sdkmanager;
+
+import com.android.prefs.AndroidLocation;
+import com.android.sdklib.SdkConstants;
+import com.android.sdklib.SdkManager;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+
+public class SdkManagerTestUtil {
+    /**
+     * Build enough of a skeleton SDK to make the tests pass.
+     *<p>
+     * Ideally this wouldn't touch the file system, but I'm not inclined to
+     * fiddle around with mock file systems just at the moment.
+     *
+     * @return an sdk manager to a fake sdk
+     * @throws IOException
+     */
+    public static File makeFakeSdk(File fakeSdk) throws IOException {
+        fakeSdk.delete();
+        fakeSdk.mkdirs();
+        AndroidLocation.resetFolder();
+        System.setProperty("user.home", fakeSdk.getAbsolutePath());
+        File addonsDir = new File(fakeSdk, SdkConstants.FD_ADDONS);
+        addonsDir.mkdir();
+        File toolsLibEmuDir = new File(fakeSdk, SdkConstants.OS_SDK_TOOLS_LIB_FOLDER + "emulator");
+        toolsLibEmuDir.mkdirs();
+        new File(toolsLibEmuDir, "snapshots.img").createNewFile();
+        File platformsDir = new File(fakeSdk, SdkConstants.FD_PLATFORMS);
+
+        // Creating a fake target here on down
+        File targetDir = new File(platformsDir, "v0_0");
+        targetDir.mkdirs();
+        new File(targetDir, SdkConstants.FN_FRAMEWORK_LIBRARY).createNewFile();
+        new File(targetDir, SdkConstants.FN_FRAMEWORK_AIDL).createNewFile();
+        new File(targetDir, SdkConstants.FN_SOURCE_PROP).createNewFile();
+        File buildProp = new File(targetDir, SdkConstants.FN_BUILD_PROP);
+        FileWriter out = new FileWriter(buildProp);
+        out.write(SdkManager.PROP_VERSION_RELEASE + "=0.0\n");
+        out.write(SdkManager.PROP_VERSION_SDK + "=0\n");
+        out.write(SdkManager.PROP_VERSION_CODENAME + "=REL\n");
+        out.close();
+        File imagesDir = new File(targetDir, "images");
+        imagesDir.mkdirs();
+        new File(imagesDir, "userdata.img").createNewFile();
+        File skinsDir = new File(targetDir, "skins");
+        File hvgaDir = new File(skinsDir, "HVGA");
+        hvgaDir.mkdirs();
+        return fakeSdk;
+    }
+
+    /**
+     * Recursive delete directory. Mostly for fake SDKs.
+     *
+     * @param root directory to delete
+     */
+    public static void deleteDir(File root) {
+        if (root.exists()) {
+            for (File file : root.listFiles()) {
+                if (file.isDirectory()) {
+                    deleteDir(file);
+                } else {
+                    file.delete();
+                }
+            }
+            root.delete();
+        }
+    }
+
+}
index b2c1326..09e3502 100644 (file)
@@ -250,6 +250,13 @@ public final class SdkConstants {
     public final static String OS_SDK_TOOLS_LIB_FOLDER =
             OS_SDK_TOOLS_FOLDER + FD_LIB + File.separator;
 
+    /**
+     * Path of the lib directory relative to the sdk folder, or to a platform
+     * folder. This is an OS path, ending with a separator.
+     */
+    public final static String OS_SDK_TOOLS_LIB_EMULATOR_FOLDER = OS_SDK_TOOLS_LIB_FOLDER
+            + "emulator" + File.separator;
+
     /** Path of the platform tools directory relative to the sdk folder.
      *  This is an OS path, ending with a separator. */
     public final static String OS_SDK_PLATFORM_TOOLS_FOLDER = FD_PLATFORM_TOOLS + File.separator;
index 27d849c..eba8e07 100644 (file)
@@ -29,6 +29,7 @@ import com.android.sdklib.io.FileWrapper;
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.FileWriter;
 import java.io.FilenameFilter;
@@ -108,6 +109,13 @@ public final class AvdManager {
      * @see #AVD_INI_IMAGES_1
      */
     public final static String AVD_INI_IMAGES_2 = "image.sysdir.2"; //$NON-NLS-1$
+    /**
+     * AVD/config.ini key name representing the presence of the snapshots file.
+     * This property is for UI purposes only. It is not used by the emulator.
+     *
+     * @see #SNAPSHOTS_IMG
+     */
+    public final static String AVD_INI_SNAPSHOT_PRESENT = "snapshot.present"; //$NON-NLS-1$
 
     /**
      * Pattern to match pixel-sized skin "names", e.g. "320x480".
@@ -117,6 +125,7 @@ public final class AvdManager {
     private final static String USERDATA_IMG = "userdata.img"; //$NON-NLS-1$
     private final static String CONFIG_INI = "config.ini"; //$NON-NLS-1$
     private final static String SDCARD_IMG = "sdcard.img"; //$NON-NLS-1$
+    private final static String SNAPSHOTS_IMG = "snapshots.img"; //$NON-NLS-1$
 
     private final static String INI_EXTENSION = ".ini"; //$NON-NLS-1$
     private final static Pattern INI_NAME_PATTERN = Pattern.compile("(.+)\\" + //$NON-NLS-1$
@@ -486,6 +495,19 @@ public final class AvdManager {
     }
 
     /**
+     * Creates a new AVD, but with no snapshot.
+     *
+     * See {@link #createAvd(File, String, IAndroidTarget, String, String, Map, boolean, boolean, ISdkLog)}
+     **/
+    @Deprecated
+    public AvdInfo createAvd(File avdFolder, String name, IAndroidTarget target, String skinName,
+            String sdcard, Map<String, String> hardwareConfig, boolean removePrevious,
+            ISdkLog log) {
+        return createAvd(avdFolder, name, target, skinName, sdcard, hardwareConfig, removePrevious,
+                false, log);
+    }
+
+    /**
      * Creates a new AVD. It is expected that there is no existing AVD with this name already.
      *
      * @param avdFolder the data folder for the AVD. It will be created as needed.
@@ -496,13 +518,14 @@ public final class AvdManager {
      *        an existing sdcard image or a sdcard size (\d+, \d+K, \dM).
      * @param hardwareConfig the hardware setup for the AVD. Can be null to use defaults.
      * @param removePrevious If true remove any previous files.
+     * @param createSnapshot If true copy a blank snapshot image into the AVD.
      * @param log the log object to receive action logs. Cannot be null.
      * @return The new {@link AvdInfo} in case of success (which has just been added to the
      *         internal list) or null in case of failure.
      */
     public AvdInfo createAvd(File avdFolder, String name, IAndroidTarget target,
             String skinName, String sdcard, Map<String,String> hardwareConfig,
-            boolean removePrevious, ISdkLog log) {
+            boolean removePrevious, boolean createSnapshot, ISdkLog log) {
         if (log == null) {
             throw new IllegalArgumentException("log cannot be null");
         }
@@ -549,20 +572,9 @@ public final class AvdManager {
                 needCleanup = true;
                 return null;
             }
-
-            FileInputStream fis = new FileInputStream(userdataSrc);
-
             File userdataDest = new File(avdFolder, USERDATA_IMG);
-            FileOutputStream fos = new FileOutputStream(userdataDest);
-
-            byte[] buffer = new byte[4096];
-            int count;
-            while ((count = fis.read(buffer)) != -1) {
-                fos.write(buffer, 0, count);
-            }
 
-            fos.close();
-            fis.close();
+            copyImageFile(userdataSrc, userdataDest);
 
             // Config file.
             HashMap<String, String> values = new HashMap<String, String>();
@@ -572,6 +584,22 @@ public final class AvdManager {
                 return null;
             }
 
+            // Create the snapshot file
+            if (createSnapshot) {
+                String toolsLib = mSdkManager.getLocation() + File.separator
+                        + SdkConstants.OS_SDK_TOOLS_LIB_EMULATOR_FOLDER;
+                File snapshotBlank = new File(toolsLib, SNAPSHOTS_IMG);
+                if (snapshotBlank.exists() == false) {
+                    log.error(null, "Unable to find a '%2$s%1$s' file to copy into the AVD folder.",
+                            SNAPSHOTS_IMG, toolsLib);
+                    needCleanup = true;
+                    return null;
+                }
+                File snapshotDest = new File(avdFolder, SNAPSHOTS_IMG);
+                copyImageFile(snapshotBlank, snapshotDest);
+                values.put(AVD_INI_SNAPSHOT_PRESENT, "true");
+            }
+
             // Now the skin.
             if (skinName == null || skinName.length() == 0) {
                 skinName = target.getDefaultSkin();
@@ -804,6 +832,28 @@ public final class AvdManager {
         return null;
     }
 
+    /** Copy the nominated file to the given destination.
+     * @param source
+     * @param destination
+     *
+     * @throws FileNotFoundException
+     * @throws IOException
+     */
+    private void copyImageFile(File source, File destination)
+            throws FileNotFoundException, IOException {
+        FileInputStream fis = new FileInputStream(source);
+        FileOutputStream fos = new FileOutputStream(destination);
+
+        byte[] buffer = new byte[4096];
+        int count;
+        while ((count = fis.read(buffer)) != -1) {
+            fos.write(buffer, 0, count);
+        }
+
+        fos.close();
+        fis.close();
+    }
+
     /**
      * Returns the path to the target images folder as a relative path to the SDK, if the folder
      * is not empty. If the image folder is empty or does not exist, <code>null</code> is returned.
@@ -1203,6 +1253,8 @@ public final class AvdManager {
             }
         }
 
+        // TODO: What about missing sdcard, skins, etc?
+
         AvdStatus status;
 
         if (avdPath == null) {
index 6dbdca6..4411034 100644 (file)
@@ -23,8 +23,8 @@ import com.android.sdklib.ISdkLog;
 import com.android.sdklib.SdkConstants;
 import com.android.sdklib.SdkManager;
 import com.android.sdklib.internal.avd.AvdManager;
-import com.android.sdklib.internal.avd.HardwareProperties;
 import com.android.sdklib.internal.avd.AvdManager.AvdInfo;
+import com.android.sdklib.internal.avd.HardwareProperties;
 import com.android.sdklib.internal.avd.HardwareProperties.HardwareProperty;
 import com.android.sdklib.internal.project.ProjectProperties;
 import com.android.sdklib.io.FileWrapper;
@@ -73,8 +73,8 @@ import java.io.File;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.TreeMap;
 import java.util.Map.Entry;
+import java.util.TreeMap;
 import java.util.regex.Matcher;
 
 /**
@@ -110,6 +110,8 @@ final class AvdCreationDialog extends GridDialog {
     private Button mBrowseSdCard;
     private Button mSdCardFileRadio;
 
+    private Button mSnapshotCheck;
+
     private Button mSkinListRadio;
     private Combo mSkinCombo;
 
@@ -346,6 +348,22 @@ final class AvdCreationDialog extends GridDialog {
         mSdCardSizeRadio.setSelection(true);
         enableSdCardWidgets(true);
 
+        // --- snapshot group
+
+        label = new Label(parent, SWT.NONE);
+        label.setText("Snapshot:");
+        label.setLayoutData(new GridData(GridData.BEGINNING, GridData.BEGINNING,
+            false, false));
+
+        final Group snapshotGroup = new Group(parent, SWT.NONE);
+        snapshotGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+        snapshotGroup.setLayout(new GridLayout(3, false));
+
+        mSnapshotCheck = new Button(snapshotGroup, SWT.CHECK);
+        mSnapshotCheck.setText("Enabled");
+        mSnapshotCheck.setToolTipText(
+                "Emulator's state will be persisted between emulator executions");
+
         // --- skin group
         label = new Label(parent, SWT.NONE);
         label.setText("Skin:");
@@ -715,6 +733,11 @@ final class AvdCreationDialog extends GridDialog {
             }
         }
 
+        String snapshots = props.get(AvdManager.AVD_INI_SNAPSHOT_PRESENT);
+        if (snapshots != null && snapshots.length() > 0) {
+            mSnapshotCheck.setSelection(snapshots.equals("true"));
+        }
+
         mProperties.clear();
         mProperties.putAll(props);
 
@@ -723,6 +746,7 @@ final class AvdCreationDialog extends GridDialog {
         mProperties.remove(AvdManager.AVD_INI_SKIN_NAME);
         mProperties.remove(AvdManager.AVD_INI_SDCARD_SIZE);
         mProperties.remove(AvdManager.AVD_INI_SDCARD_PATH);
+        mProperties.remove(AvdManager.AVD_INI_SNAPSHOT_PRESENT);
         mProperties.remove(AvdManager.AVD_INI_IMAGES_1);
         mProperties.remove(AvdManager.AVD_INI_IMAGES_2);
         mHardwareViewer.refresh();
@@ -1138,6 +1162,7 @@ final class AvdCreationDialog extends GridDialog {
         }
 
         boolean force = mForceCreation.getSelection();
+        boolean snapshot = mSnapshotCheck.getSelection();
 
         boolean success = false;
         AvdInfo avdInfo = mAvdManager.createAvd(
@@ -1148,6 +1173,7 @@ final class AvdCreationDialog extends GridDialog {
                 sdName,
                 mProperties,
                 force,
+                snapshot,
                 log);
 
         success = avdInfo != null;
index a845056..409c25d 100644 (file)
@@ -127,6 +127,11 @@ final class AvdDetailsDialog extends Dialog {
                         displayValue(c, "SD Card:", sdcard);
                     }
 
+                    String snapshot = properties.get(AvdManager.AVD_INI_SNAPSHOT_PRESENT);
+                    if (snapshot != null) {
+                        displayValue(c, "Snapshot:", snapshot);
+                    }
+
                     // display other hardware
                     HashMap<String, String> copy = new HashMap<String, String>(properties);
                     // remove stuff we already displayed (or that we don't want to display)
index e002ef3..5e26a41 100644 (file)
@@ -1037,9 +1037,17 @@ public final class AvdSelector {
             list.add(path);
             list.add("-avd");                             //$NON-NLS-1$
             list.add(avdName);
-            if (dialog.getWipeData()) {
+            if (dialog.hasWipeData()) {
                 list.add("-wipe-data");                   //$NON-NLS-1$
             }
+            if (dialog.hasSnapshot()) {
+                if (!dialog.hasSnapshotLaunch()) {
+                    list.add("-no-snapshot-load");
+                }
+                if (!dialog.hasSnapshotSave()) {
+                    list.add("-no-snapshot-save");
+                }
+            }
             float scale = dialog.getScale();
             if (scale != 0.f) {
                 // do the rounding ourselves. This is because %.1f will write .4899 as .4
index a2a9218..77f47d1 100644 (file)
@@ -56,13 +56,14 @@ import java.util.regex.Pattern;
  * <li>-wipe-data</li>
  * <li>-scale</li>
  * </ul>
- *
  * Values are stored (in the class as static field) to be reused while the app is still running.
- * The Monitor dpi is stored in the settings if availabe.
+ * The Monitor dpi is stored in the settings if available.
  */
 final class AvdStartDialog extends GridDialog {
     // static field to reuse values during the same session.
     private static boolean sWipeData = false;
+    private static boolean sSnapshotSave = true;
+    private static boolean sSnapshotLaunch = true;
     private static int sMonitorDpi = 72; // used if there's no setting controller.
     private static final Map<String, String> sSkinScaling = new HashMap<String, String>();
 
@@ -84,6 +85,10 @@ final class AvdStartDialog extends GridDialog {
     private String mSkinDisplay;
     private boolean mEnableScaling = true;
     private Label mScaleField;
+    private boolean mHasSnapshot = true;
+    private boolean mSnapshotSave = true;
+    private boolean mSnapshotLaunch = true;
+    private Button mSnapshotLaunchCheckbox;
 
     AvdStartDialog(Shell parentShell, AvdInfo avd, String sdkLocation,
             SettingsController settingsController) {
@@ -101,7 +106,7 @@ final class AvdStartDialog extends GridDialog {
         computeSkinData();
     }
 
-    public boolean getWipeData() {
+    public boolean hasWipeData() {
         return mWipeData;
     }
 
@@ -239,6 +244,36 @@ final class AvdStartDialog extends GridDialog {
             @Override
             public void widgetSelected(SelectionEvent arg0) {
                 mWipeData = wipeButton.getSelection();
+                updateSnapshotLaunchAvailability();
+            }
+        });
+
+        Map<String, String> prop = mAvd.getProperties();
+        String snapshotPresent = prop.get(AvdManager.AVD_INI_SNAPSHOT_PRESENT);
+        mHasSnapshot = (snapshotPresent != null) && snapshotPresent.equals("true");
+
+        mSnapshotLaunchCheckbox = new Button(parent, SWT.CHECK);
+        mSnapshotLaunchCheckbox.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
+        gd.horizontalSpan = 2;
+        mSnapshotLaunchCheckbox.setText("Launch from snapshot");
+        updateSnapshotLaunchAvailability();
+        mSnapshotLaunchCheckbox.addSelectionListener(new SelectionAdapter() {
+            @Override
+            public void widgetSelected(SelectionEvent arg0) {
+                mSnapshotLaunch = mSnapshotLaunchCheckbox.getSelection();
+            }
+        });
+
+        final Button snapshotSaveCheckbox = new Button(parent, SWT.CHECK);
+        snapshotSaveCheckbox.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
+        gd.horizontalSpan = 2;
+        snapshotSaveCheckbox.setText("Save to snapshot");
+        snapshotSaveCheckbox.setSelection((mSnapshotSave = sSnapshotSave) && mHasSnapshot);
+        snapshotSaveCheckbox.setEnabled(mHasSnapshot);
+        snapshotSaveCheckbox.addSelectionListener(new SelectionAdapter() {
+            @Override
+            public void widgetSelected(SelectionEvent arg0) {
+                mSnapshotSave = snapshotSaveCheckbox.getSelection();
             }
         });
 
@@ -303,6 +338,14 @@ final class AvdStartDialog extends GridDialog {
         // and then the wipe-data checkbox
         sWipeData = mWipeData;
 
+        // and the snapshot handling if those checkboxes are enabled.
+        if (mHasSnapshot) {
+            sSnapshotSave = mSnapshotSave;
+            if (!mWipeData) {
+                sSnapshotLaunch = mSnapshotLaunch;
+            }
+        }
+
         // finally continue with the ok action
         super.okPressed();
     }
@@ -520,4 +563,36 @@ final class AvdStartDialog extends GridDialog {
 
         return false;
     }
+
+    /**
+     * @return Whether there's a snapshot file available.
+     */
+    public boolean hasSnapshot() {
+        return mHasSnapshot;
+    }
+
+    /**
+     * @return Whether to launch and load snapshot.
+     */
+    public boolean hasSnapshotLaunch() {
+        return mSnapshotLaunch && !hasWipeData();
+    }
+
+    /**
+     * @return Whether to preserve emulator state to snapshot.
+     */
+    public boolean hasSnapshotSave() {
+        return mSnapshotSave;
+    }
+
+    /**
+     * Updates snapshot launch availability, for when mWipeData value changes.
+     */
+    private void updateSnapshotLaunchAvailability() {
+        boolean enabled = !mWipeData && mHasSnapshot;
+        mSnapshotLaunchCheckbox.setEnabled(enabled);
+        mSnapshotLaunch = enabled && sSnapshotLaunch;
+        mSnapshotLaunchCheckbox.setSelection(mSnapshotLaunch);
+    }
+
 }