Merge change 3572

* changes:
  Changing plugin to use the draw event (not drawContext)
diff --git a/samples/ApiDemos/AndroidManifest.xml b/samples/ApiDemos/AndroidManifest.xml
index fb34afa..9d25512 100644
--- a/samples/ApiDemos/AndroidManifest.xml
+++ b/samples/ApiDemos/AndroidManifest.xml
@@ -1562,6 +1562,13 @@
             </intent-filter>
         </activity>
 
+        <activity android:name=".graphics.ColorFilters" android:label="Graphics/ColorFilters">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
         <activity android:name=".graphics.CreateBitmap" android:label="Graphics/CreateBitmap">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/samples/ApiDemos/res/drawable/btn_check_off.png b/samples/ApiDemos/res/drawable/btn_check_off.png
new file mode 100644
index 0000000..56d3861
--- /dev/null
+++ b/samples/ApiDemos/res/drawable/btn_check_off.png
Binary files differ
diff --git a/samples/ApiDemos/res/drawable/btn_check_on.png b/samples/ApiDemos/res/drawable/btn_check_on.png
new file mode 100644
index 0000000..791ac1d
--- /dev/null
+++ b/samples/ApiDemos/res/drawable/btn_check_on.png
Binary files differ
diff --git a/samples/ApiDemos/res/drawable/btn_circle_normal.png b/samples/ApiDemos/res/drawable/btn_circle_normal.png
new file mode 100644
index 0000000..fc5af1c
--- /dev/null
+++ b/samples/ApiDemos/res/drawable/btn_circle_normal.png
Binary files differ
diff --git a/samples/ApiDemos/res/drawable/btn_default_normal.9.png b/samples/ApiDemos/res/drawable/btn_default_normal.9.png
new file mode 100644
index 0000000..a2d5ccd
--- /dev/null
+++ b/samples/ApiDemos/res/drawable/btn_default_normal.9.png
Binary files differ
diff --git a/samples/ApiDemos/src/com/example/android/apis/graphics/ColorFilters.java b/samples/ApiDemos/src/com/example/android/apis/graphics/ColorFilters.java
new file mode 100644
index 0000000..92d18ba
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/graphics/ColorFilters.java
@@ -0,0 +1,184 @@
+/*
+ * 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.graphics;
+
+import com.example.android.apis.R;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.*;
+import android.graphics.drawable.*;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.*;
+
+public class ColorFilters extends GraphicsActivity {
+    
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(new SampleView(this));
+        
+    }
+    
+    private static class SampleView extends View {
+        private Activity mActivity;
+        private Drawable mDrawable;
+        private Drawable[] mDrawables;
+        private Paint mPaint;
+        private Paint mPaint2;
+        private float mPaintTextOffset;
+        private int[] mColors;
+        private PorterDuff.Mode[] mModes;
+        private int mModeIndex;
+
+        private static void addToTheRight(Drawable curr, Drawable prev) {
+            Rect r = prev.getBounds();
+            int x = r.right + 12;
+            int center = (r.top + r.bottom) >> 1;
+            int h = curr.getIntrinsicHeight();
+            int y = center - (h >> 1);
+            
+            curr.setBounds(x, y, x + curr.getIntrinsicWidth(), y + h);
+        }
+
+        public SampleView(Activity activity) {
+            super(activity);
+            mActivity = activity;
+            Context context = activity;
+            setFocusable(true);
+            
+            mDrawable = context.getResources().getDrawable(R.drawable.btn_default_normal);
+            mDrawable.setBounds(0, 0, 150, 48);
+            mDrawable.setDither(true);
+
+            int[] resIDs = new int[] {
+                R.drawable.btn_circle_normal,
+                R.drawable.btn_check_off,
+                R.drawable.btn_check_on
+            };
+            mDrawables = new Drawable[resIDs.length];
+            Drawable prev = mDrawable;
+            for (int i = 0; i < resIDs.length; i++) {
+                mDrawables[i] = context.getResources().getDrawable(resIDs[i]);
+                mDrawables[i].setDither(true);
+                addToTheRight(mDrawables[i], prev);
+                prev = mDrawables[i];
+            }
+
+            mPaint = new Paint();
+            mPaint.setAntiAlias(true);
+            mPaint.setTextSize(16);
+            mPaint.setTextAlign(Paint.Align.CENTER);
+            
+            mPaint2 = new Paint(mPaint);
+            mPaint2.setAlpha(64);
+            
+            Paint.FontMetrics fm = mPaint.getFontMetrics();
+            mPaintTextOffset = (fm.descent + fm.ascent) * 0.5f;
+            
+            mColors = new int[] {
+                0,
+                0xCC0000FF,
+                0x880000FF,
+                0x440000FF,
+                0xFFCCCCFF,
+                0xFF8888FF,
+                0xFF4444FF,
+            };
+
+            mModes = new PorterDuff.Mode[] {
+                PorterDuff.Mode.SRC_ATOP,
+                PorterDuff.Mode.MULTIPLY,
+            };
+            mModeIndex = 0;
+            
+            updateTitle();
+        }
+        
+        private void swapPaintColors() {
+            if (mPaint.getColor() == 0xFF000000) {
+                mPaint.setColor(0xFFFFFFFF);
+                mPaint2.setColor(0xFF000000);
+            } else {
+                mPaint.setColor(0xFF000000);
+                mPaint2.setColor(0xFFFFFFFF);
+            }
+            mPaint2.setAlpha(64);
+        }
+        
+        private void updateTitle() {
+            mActivity.setTitle(mModes[mModeIndex].toString());
+        }
+        
+        private void drawSample(Canvas canvas, ColorFilter filter) {
+            Rect r = mDrawable.getBounds();
+            float x = (r.left + r.right) * 0.5f;
+            float y = (r.top + r.bottom) * 0.5f - mPaintTextOffset;
+
+            mDrawable.setColorFilter(filter);
+            mDrawable.draw(canvas);
+            canvas.drawText("Label", x+1, y+1, mPaint2);
+            canvas.drawText("Label", x, y, mPaint);
+            
+            for (Drawable dr : mDrawables) {
+                dr.setColorFilter(filter);
+                dr.draw(canvas);
+            }
+        }
+        
+        @Override protected void onDraw(Canvas canvas) {
+            canvas.drawColor(0xFFCCCCCC);            
+
+            canvas.translate(8, 12);
+            for (int color : mColors) {
+                ColorFilter filter;
+                if (color == 0) {
+                    filter = null;
+                } else {
+                    filter = new PorterDuffColorFilter(color,
+                                                       mModes[mModeIndex]);
+                }
+                drawSample(canvas, filter);
+                canvas.translate(0, 55);
+            }
+        }
+
+        @Override
+        public boolean onTouchEvent(MotionEvent event) {
+            float x = event.getX();
+            float y = event.getY();
+            switch (event.getAction()) {
+                case MotionEvent.ACTION_DOWN:
+                    break;
+                case MotionEvent.ACTION_MOVE:
+                    break;
+                case MotionEvent.ACTION_UP:
+                    // update mode every other time we change paint colors
+                    if (mPaint.getColor() == 0xFFFFFFFF) {
+                        mModeIndex = (mModeIndex + 1) % mModes.length;
+                        updateTitle();
+                    }
+                    swapPaintColors();
+                    invalidate();
+                    break;
+            }
+            return true;
+        }
+    }
+}
+
diff --git a/simulator/app/PropertyServer.cpp b/simulator/app/PropertyServer.cpp
index 4b1aedd..0047b5d 100644
--- a/simulator/app/PropertyServer.cpp
+++ b/simulator/app/PropertyServer.cpp
@@ -110,6 +110,8 @@
         { "ro.EMPTY_APP_MEM", "16384" },
         { "ro.HOME_APP_ADJ", "4" },
         { "ro.HOME_APP_MEM", "4096" },
+        { "ro.BACKUP_APP_ADJ", "2" },
+        { "ro.BACKUP_APP_MEM", "4096" },
         //{ "init.svc.adbd", "running" },       // causes ADB-JDWP
         { "init.svc.usbd", "running" },
         { "init.svc.debuggerd", "running" },
@@ -180,7 +182,7 @@
             if (strlen(prop.value) >= PROPERTY_VALUE_MAX) {
                 fprintf(stderr,
                     "GLITCH: properties table holds '%s' '%s' (len=%d)\n",
-                    prop.key, prop.value, strlen(prop.value));
+                    prop.key, prop.value, (int) strlen(prop.value));
                 abort();
             }
             assert(strlen(prop.value) < PROPERTY_VALUE_MAX);
@@ -323,7 +325,7 @@
 
         if (actual != PROPERTY_KEY_MAX) {
             fprintf(stderr, "Bad read on get: %d of %d\n",
-                actual, PROPERTY_KEY_MAX);
+                (int) actual, PROPERTY_KEY_MAX);
             return false;
         }
         if (GetProperty(reqBuf, valueBuf+1))
@@ -340,7 +342,7 @@
         actual = read(fd, reqBuf, PROPERTY_KEY_MAX + PROPERTY_VALUE_MAX);
         if (actual != PROPERTY_KEY_MAX + PROPERTY_VALUE_MAX) {
             fprintf(stderr, "Bad read on set: %d of %d\n",
-                actual, PROPERTY_KEY_MAX + PROPERTY_VALUE_MAX);
+                (int) actual, PROPERTY_KEY_MAX + PROPERTY_VALUE_MAX);
             return false;
         }
         //printf("SET property '%s'\n", reqBuf);
diff --git a/tools/mkstubs/src/com/android/mkstubs/FilterClassAdapter.java b/tools/mkstubs/src/com/android/mkstubs/FilterClassAdapter.java
index d3b06f7..3874e6b 100644
--- a/tools/mkstubs/src/com/android/mkstubs/FilterClassAdapter.java
+++ b/tools/mkstubs/src/com/android/mkstubs/FilterClassAdapter.java
@@ -26,7 +26,7 @@
 
 /**
  * A class visitor that filters out all members (fields, methods and inner classes) that are
- * either private or rejected by the {@link Filter}.
+ * either private, default-access or rejected by the {@link Filter}.
  */
 class FilterClassAdapter extends ClassAdapter {
 
@@ -50,12 +50,12 @@
     public void visitEnd() {
         super.visitEnd();
     }
-    
+
     /**
      * Visits a field.
-     * 
+     *
      * {@inheritDoc}
-     * 
+     *
      * Examples:
      * name = mArg
      * desc = Ljava/Lang/String;
@@ -64,11 +64,11 @@
     @Override
     public FieldVisitor visitField(int access, String name, String desc,
             String signature, Object value) {
-        // exclude private fields
-        if ((access & Opcodes.ACC_PRIVATE) != 0) {
+        // only accept public/protected fields
+        if ((access & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED)) == 0) {
             return null;
         }
-        
+
         // filter on field name
         String filterName = String.format("%s#%s", mClassName, name);
 
@@ -76,7 +76,7 @@
             System.out.println("- Remove field " + filterName);
             return null;
         }
-        
+
         // TODO we should produce an error if a filtered desc/signature is being used.
 
         return super.visitField(access, name, desc, signature, value);
@@ -84,9 +84,9 @@
 
     /**
      * Visits a method.
-     * 
+     *
      * {@inheritDoc}
-     * 
+     *
      * Examples:
      * name = <init>
      * desc = ()V
@@ -96,11 +96,11 @@
     public MethodVisitor visitMethod(int access, String name, String desc,
             String signature, String[] exceptions) {
 
-        // exclude private methods
-        if ((access & Opcodes.ACC_PRIVATE) != 0) {
+        // only accept public/protected methods
+        if ((access & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED)) == 0) {
             return null;
         }
-        
+
         // filter on method name using the non-generic descriptor
         String filterName = String.format("%s#%s%s", mClassName, name, desc);
 
@@ -112,7 +112,7 @@
         // filter on method name using the generic signature
         if (signature != null) {
             filterName = String.format("%s#%s%s", mClassName, name, signature);
-    
+
             if (!mFilter.accept(filterName)) {
                 System.out.println("- Remove method " + filterName);
                 return null;
@@ -126,7 +126,7 @@
 
     @Override
     public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
-        
+
         // TODO produce an error if a filtered annotation type is being used
         return super.visitAnnotation(desc, visible);
     }
@@ -139,8 +139,8 @@
     @Override
     public void visitInnerClass(String name, String outerName, String innerName, int access) {
 
-        // exclude private methods
-        if ((access & Opcodes.ACC_PRIVATE) != 0) {
+        // only accept public/protected inner classes
+        if ((access & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED)) == 0) {
             return;
         }
 
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java
index bd76a4c..761c236 100755
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java
@@ -74,14 +74,15 @@
     }

 

     /**

-     * Creates a new platform package based on an actual {@link IAndroidTarget} (with

+     * Creates a new platform package based on an actual {@link IAndroidTarget} (which

      * {@link IAndroidTarget#isPlatform()} false) from the {@link SdkManager}.

      * This is used to list local SDK folders.

      */

     AddonPackage(IAndroidTarget target) {

         super(  null,                       //source

                 0,                          //revision

-                target.getDescription(),  //description

+                null,                       //license

+                target.getDescription(),    //description

                 null,                       //descUrl

                 Os.getCurrentOs(),          //archiveOs

                 Arch.getCurrentArch(),      //archiveArch

diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Archive.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Archive.java
index 9686cbd..bd6b5be 100755
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Archive.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Archive.java
@@ -16,8 +16,16 @@
 

 package com.android.sdklib.internal.repository;

 

+import java.io.File;

+import java.io.FileInputStream;

+import java.io.FileOutputStream;

+import java.io.IOException;

+import java.io.InputStream;

+import java.net.URL;

 import java.security.MessageDigest;

 import java.security.NoSuchAlgorithmException;

+import java.util.zip.ZipEntry;

+import java.util.zip.ZipInputStream;

 

 

 /**

@@ -32,6 +40,8 @@
  */

 public class Archive implements IDescription {

 

+    public static final int NUM_MONITOR_INC = 100;

+

     /** The checksum type. */

     public enum ChecksumType {

         /** A SHA1 checksum, represented as a 40-hex string. */

@@ -269,5 +279,450 @@
 

         return true;

     }

+

+    /**

+     * Install this {@link Archive}s.

+     * The archive will be skipped if it is incompatible.

+     *

+     * @return True if the archive was installed, false otherwise.

+     */

+    public boolean install(String osSdkRoot, ITaskMonitor monitor) {

+

+        File archiveFile = null;

+        try {

+            String name = getParentPackage().getShortDescription();

+

+            // TODO: we should not see this test fail if we had the filter UI above.

+            if (!isCompatible()) {

+                monitor.setResult("Skipping incompatible archive: %1$s", name);

+                return false;

+            }

+

+            archiveFile = downloadFile(monitor);

+            if (archiveFile != null) {

+                if (unarchive(osSdkRoot, archiveFile, monitor)) {

+                    monitor.setResult("Installed: %1$s", name);

+                    return true;

+                }

+            }

+

+        } finally {

+            // Delete the temp archive if it exists

+            deleteFileOrFolder(archiveFile);

+        }

+

+        return false;

+    }

+

+    /**

+     * Downloads an archive and returns the temp file with it.

+     * Caller is responsible with deleting the temp file when done.

+     */

+    private File downloadFile(ITaskMonitor monitor) {

+

+        File tmpFileToDelete = null;

+        try {

+            File tmpFile = File.createTempFile("sdkupload", ".bin"); //$NON-NLS-1$ //$NON-NLS-2$

+            tmpFileToDelete = tmpFile;

+

+            String name = getParentPackage().getShortDescription();

+            String desc = String.format("Downloading %1$s", name);

+            monitor.setDescription(desc);

+

+            String link = getUrl();

+            if (!link.startsWith("http://")                          //$NON-NLS-1$

+                    && !link.startsWith("https://")                  //$NON-NLS-1$

+                    && !link.startsWith("ftp://")) {                 //$NON-NLS-1$

+                // Make the URL absolute by prepending the source

+                Package pkg = getParentPackage();

+                RepoSource src = pkg.getParentSource();

+                if (src == null) {

+                    monitor.setResult("Internal error: no source for archive %1$s", name);

+                    return null;

+                }

+

+                // take the URL to the repository.xml and remove the last component

+                // to get the base

+                String repoXml = src.getUrl();

+                int pos = repoXml.lastIndexOf('/');

+                String base = repoXml.substring(0, pos + 1);

+

+                link = base + link;

+            }

+

+            if (fetchUrl(tmpFile, link, desc, monitor)) {

+                // Fetching was successful, don't delete the temp file here!

+                tmpFileToDelete = null;

+                return tmpFile;

+            }

+

+        } catch (IOException e) {

+            monitor.setResult(e.getMessage());

+

+        } finally {

+            deleteFileOrFolder(tmpFileToDelete);

+        }

+

+        return null;

+    }

+

+    /**

+     * Actually performs the download.

+     * Also computes the SHA1 of the file on the fly.

+     * <p/>

+     * Success is defined as downloading as many bytes as was expected and having the same

+     * SHA1 as expected. Returns true on success or false if any of those checks fail.

+     * <p/>

+     * Increments the monitor by {@link #NUM_MONITOR_INC}.

+     */

+    private boolean fetchUrl(File tmpFile,

+            String urlString,

+            String description,

+            ITaskMonitor monitor) {

+        URL url;

+

+        description += " (%1$d%%, %2$.0f KiB/s, %3$d %4$s left)";

+

+        FileOutputStream os = null;

+        InputStream is = null;

+        try {

+            url = new URL(urlString);

+            is = url.openStream();

+            os = new FileOutputStream(tmpFile);

+

+            MessageDigest digester = getChecksumType().getMessageDigest();

+

+            byte[] buf = new byte[65536];

+            int n;

+

+            long total = 0;

+            long size = getSize();

+            long inc = size / NUM_MONITOR_INC;

+            long next_inc = inc;

+

+            long startMs = System.currentTimeMillis();

+            long nextMs = startMs + 2000;  // start update after 2 seconds

+

+            while ((n = is.read(buf)) >= 0) {

+                if (n > 0) {

+                    os.write(buf, 0, n);

+                    digester.update(buf, 0, n);

+                }

+

+                long timeMs = System.currentTimeMillis();

+

+                total += n;

+                if (total >= next_inc) {

+                    monitor.incProgress(1);

+                    next_inc += inc;

+                }

+

+                if (timeMs > nextMs) {

+                    long delta = timeMs - startMs;

+                    if (total > 0 && delta > 0) {

+                        // percent left to download

+                        int percent = (int) (100 * total / size);

+                        // speed in KiB/s

+                        float speed = (float)total / (float)delta * (1000.f / 1024.f);

+                        // time left to download the rest at the current KiB/s rate

+                        int timeLeft = (speed > 1e-3) ?

+                                               (int)(((size - total) / 1024.0f) / speed) :

+                                               0;

+                        String timeUnit = "seconds";

+                        if (timeLeft > 120) {

+                            timeUnit = "minutes";

+                            timeLeft /= 60;

+                        }

+

+                        monitor.setDescription(description, percent, speed, timeLeft, timeUnit);

+                    }

+                    nextMs = timeMs + 1000;  // update every second

+                }

+

+                if (monitor.isCancelRequested()) {

+                    monitor.setResult("Download aborted by user at %1$d bytes.", total);

+                    return false;

+                }

+

+            }

+

+            if (total != size) {

+                monitor.setResult("Download finished with wrong size. Expected %1$d bytes, got %2$d bytes.",

+                        size, total);

+                return false;

+            }

+

+            // Create an hex string from the digest

+            byte[] digest = digester.digest();

+            n = digest.length;

+            String hex = "0123456789abcdef";                     //$NON-NLS-1$

+            char[] hexDigest = new char[n * 2];

+            for (int i = 0; i < n; i++) {

+                int b = digest[i] & 0x0FF;

+                hexDigest[i*2 + 0] = hex.charAt(b >>> 4);

+                hexDigest[i*2 + 1] = hex.charAt(b & 0x0f);

+            }

+

+            String expected = getChecksum();

+            String actual   = new String(hexDigest);

+            if (!actual.equalsIgnoreCase(expected)) {

+                monitor.setResult("Download finished with wrong checksum. Expected %1$s, got %2$s.",

+                        expected, actual);

+                return false;

+            }

+

+            return true;

+

+        } catch (Exception e) {

+            monitor.setResult(e.getMessage());

+

+        } finally {

+            if (os != null) {

+                try {

+                    os.close();

+                } catch (IOException e) {

+                    // pass

+                }

+            }

+

+            if (is != null) {

+                try {

+                    is.close();

+                } catch (IOException e) {

+                    // pass

+                }

+            }

+        }

+

+        return false;

+    }

+

+    /**

+     * Install the given archive in the given folder.

+     */

+    private boolean unarchive(String osSdkRoot, File archiveFile, ITaskMonitor monitor) {

+        String name = getParentPackage().getShortDescription();

+        String desc = String.format("Installing %1$s", name);

+        monitor.setDescription(desc);

+

+        File destFolder = getParentPackage().getInstallFolder(osSdkRoot);

+

+        File unzipDestFolder = destFolder;

+        File renamedDestFolder = null;

+

+        try {

+            // If this folder already exists, unzip in a temporary folder and then move/unlink.

+            if (destFolder.exists()) {

+                // Find a new temp folder that doesn't exist yet

+                unzipDestFolder = findTempFolder(destFolder, "new");  //$NON-NLS-1$

+

+                if (unzipDestFolder == null) {

+                    // this should not seriously happen.

+                    monitor.setResult("Failed to find a suitable temp directory similar to %1$s.",

+                            destFolder.getPath());

+                    return false;

+                }

+

+                if (!unzipDestFolder.mkdirs()) {

+                    monitor.setResult("Failed to create temp directory %1$s",

+                            unzipDestFolder.getPath());

+                    return false;

+                }

+            }

+

+            if (!unzipFolder(archiveFile, getSize(), unzipDestFolder, desc, monitor)) {

+                return false;

+            }

+

+            if (unzipDestFolder != destFolder) {

+                // Swap the old folder by the new one.

+                // Both original folders will be deleted in the finally clause below.

+                renamedDestFolder = findTempFolder(destFolder, "old");  //$NON-NLS-1$

+                if (renamedDestFolder == null) {

+                    // this should not seriously happen.

+                    monitor.setResult("Failed to find a suitable temp directory similar to %1$s.",

+                            destFolder.getPath());

+                    return false;

+                }

+

+                if (!destFolder.renameTo(renamedDestFolder)) {

+                    monitor.setResult("Failed to rename directory %1$s to %2$s",

+                            destFolder.getPath(), renamedDestFolder.getPath());

+                    return false;

+

+                }

+                if (!unzipDestFolder.renameTo(destFolder)) {

+                    monitor.setResult("Failed to rename directory %1$s to %2$s",

+                            unzipDestFolder.getPath(), destFolder.getPath());

+                    return false;

+                }

+            }

+

+            return true;

+

+        } finally {

+            // Cleanup if the unzip folder is still set.

+            deleteFileOrFolder(renamedDestFolder);

+            if (unzipDestFolder != destFolder) {

+                deleteFileOrFolder(unzipDestFolder);

+            }

+        }

+    }

+

+    private boolean unzipFolder(File archiveFile,

+            long compressedSize,

+            File unzipDestFolder,

+            String description,

+            ITaskMonitor monitor) {

+

+        description += " (%1$d%%)";

+

+        FileInputStream fis = null;

+        ZipInputStream  zis = null;

+        try {

+            fis = new FileInputStream(archiveFile);

+            zis = new ZipInputStream(fis);

+

+            // To advance the percent and the progress bar, we don't know the number of

+            // items left to unzip. However we know the size of the archive and the size of

+            // each uncompressed item. The zip file format overhead is negligible so that's

+            // a good approximation.

+            long incStep = compressedSize / NUM_MONITOR_INC;

+            long incTotal = 0;

+            long incCurr = 0;

+            int lastPercent = 0;

+

+            byte[] buf = new byte[65536];

+

+            ZipEntry entry;

+            while ((entry = zis.getNextEntry()) != null) {

+

+                String name = entry.getName();

+

+                // ZipFile entries should have forward slashes, but not all Zip

+                // implementations can be expected to do that.

+                name = name.replace('\\', '/');

+

+                File destFile = new File(unzipDestFolder, name);

+

+                if (name.endsWith("/")) {  //$NON-NLS-1$

+                    // Create directory if it doesn't exist yet. This allows us to create

+                    // empty directories.

+                    if (!destFile.isDirectory() && !destFile.mkdirs()) {

+                        monitor.setResult("Failed to create temp directory %1$s",

+                                destFile.getPath());

+                        return false;

+                    }

+                    continue;

+                } else if (name.indexOf('/') != -1) {

+                    // Otherwise it's a file in a sub-directory.

+                    // Make sure the parent directory has been created.

+                    File parentDir = destFile.getParentFile();

+                    if (!parentDir.isDirectory()) {

+                        if (!parentDir.mkdirs()) {

+                            monitor.setResult("Failed to create temp directory %1$s",

+                                    parentDir.getPath());

+                            return false;

+                        }

+                    }

+                }

+

+                FileOutputStream fos = null;

+                try {

+                    fos = new FileOutputStream(destFile);

+                    int n;

+                    while ((n = zis.read(buf)) != -1) {

+                        if (n > 0) {

+                            fos.write(buf, 0, n);

+                        }

+                    }

+                } finally {

+                    if (fos != null) {

+                        fos.close();

+                    }

+                }

+

+                // Increment progress bar to match. We update only between files.

+                for(incTotal += entry.getCompressedSize(); incCurr < incTotal; incCurr += incStep) {

+                    monitor.incProgress(1);

+                }

+

+                int percent = (int) (100 * incTotal / compressedSize);

+                if (percent != lastPercent) {

+                    monitor.setDescription(description, percent);

+                    lastPercent = percent;

+                }

+

+                if (monitor.isCancelRequested()) {

+                    return false;

+                }

+            }

+

+            return true;

+

+        } catch (IOException e) {

+            monitor.setResult("Unzip failed: %1$s", e.getMessage());

+

+        } finally {

+            if (zis != null) {

+                try {

+                    zis.close();

+                } catch (IOException e) {

+                    // pass

+                }

+            }

+            if (fis != null) {

+                try {

+                    fis.close();

+                } catch (IOException e) {

+                    // pass

+                }

+            }

+        }

+

+        return false;

+    }

+

+    /**

+     * Finds a temp folder which name is similar to the one of the ideal folder

+     * and with a ".tmpN" appended.

+     * <p/>

+     * This operation is not atomic so there's no guarantee the folder can't get

+     * created in between. This is however unlikely and the caller can assume the

+     * returned folder does not exist yet.

+     * <p/>

+     * Returns null if no such folder can be found (e.g. if all candidates exist),

+     * which is rather unlikely.

+     */

+    private File findTempFolder(File idealFolder, String suffix) {

+        String basePath = idealFolder.getPath();

+

+        for (int i = 1; i < 100; i++) {

+            File folder = new File(String.format("%1$s.%2$s%3$02d", basePath, suffix, i));  //$NON-NLS-1$

+            if (!folder.exists()) {

+                return folder;

+            }

+        }

+        return null;

+    }

+

+    /**

+     * Deletes a file or a directory.

+     * Directories are deleted recursively.

+     * The argument can be null.

+     */

+    private void deleteFileOrFolder(File fileOrFolder) {

+        if (fileOrFolder != null) {

+            if (fileOrFolder.isDirectory()) {

+                // Must delete content recursively first

+                for (File item : fileOrFolder.listFiles()) {

+                    deleteFileOrFolder(item);

+                }

+            }

+            if (!fileOrFolder.delete()) {

+                fileOrFolder.deleteOnExit();

+            }

+        }

+    }

 }

 

diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java
index 8f07255..e2c2cf5 100755
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java
@@ -49,6 +49,7 @@
     DocPackage(RepoSource source,

             int apiLevel,

             int revision,

+            String license,

             String description,

             String descUrl,

             Os archiveOs,

@@ -58,6 +59,7 @@
             String archiveChecksum) {

         super(source,

                 revision,

+                license,

                 description,

                 descUrl,

                 archiveOs,

diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LocalSdkParser.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LocalSdkParser.java
index f150510..8d067f2 100755
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LocalSdkParser.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LocalSdkParser.java
@@ -167,6 +167,7 @@
             pkg = new ToolPackage(

                     null,                       //source

                     0,                          //revision

+                    null,                       //license

                     "Tools",                    //description

                     null,                       //descUrl

                     Os.getCurrentOs(),          //archiveOs

@@ -229,6 +230,7 @@
                         null,                       //source

                         0,                          //apiLevel

                         0,                          //revision

+                        null,                       //license

                         String.format("Documentation for %1$s", found),     //description

                         null,                       //descUrl

                         Os.getCurrentOs(),          //archiveOs

diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java
index 55ecaef..4d28f08 100755
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java
@@ -40,6 +40,7 @@
 public abstract class Package implements IDescription {

 

     private final int mRevision;

+    private final String mLicense;

     private final String mDescription;

     private final String mDescUrl;

     private final Archive[] mArchives;

@@ -55,7 +56,7 @@
         mRevision    = getXmlInt   (packageNode, SdkRepository.NODE_REVISION, 0);

         mDescription = getXmlString(packageNode, SdkRepository.NODE_DESCRIPTION);

         mDescUrl     = getXmlString(packageNode, SdkRepository.NODE_DESC_URL);

-

+        mLicense     = getXmlString(packageNode, SdkRepository.NODE_LICENSE);

         mArchives = parseArchives(getFirstChild(packageNode, SdkRepository.NODE_ARCHIVES));

     }

 

@@ -65,6 +66,7 @@
      */

     public Package(RepoSource source,

             int revision,

+            String license,

             String description,

             String descUrl,

             Os archiveOs,

@@ -74,6 +76,7 @@
             String archiveChecksum) {

         mSource = source;

         mRevision = revision;

+        mLicense = license;

         mDescription = description;

         mDescUrl = descUrl;

         mArchives = new Archive[1];

@@ -142,6 +145,14 @@
 

     /**

      * Returns the optional description for all packages (platform, add-on, tool, doc) or

+     * for a lib. It is null if the element has not been specified in the repository XML.

+     */

+    public String getLicense() {

+        return mLicense;

+    }

+

+    /**

+     * Returns the optional description for all packages (platform, add-on, tool, doc) or

      * for a lib. Can be empty but not null.

      */

     public String getDescription() {

diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java
index 0d51c58..ae6bc77 100755
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java
@@ -47,14 +47,15 @@
     }

 

     /**

-     * Creates a new platform package based on an actual {@link IAndroidTarget} (with

+     * Creates a new platform package based on an actual {@link IAndroidTarget} (which

      * must have {@link IAndroidTarget#isPlatform()} true) from the {@link SdkManager}.

      * This is used to list local SDK folders.

      */

     PlatformPackage(IAndroidTarget target) {

         super(  null,                       //source

                 0,                          //revision

-                target.getDescription(),  //description

+                null,                       //license

+                target.getDescription(),    //description

                 null,                       //descUrl

                 Os.getCurrentOs(),          //archiveOs

                 Arch.getCurrentArch(),      //archiveArch

diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java
index 71e35c4..4cac706 100755
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java
@@ -44,6 +44,7 @@
      */

     ToolPackage(RepoSource source,

             int revision,

+            String license,

             String description,

             String descUrl,

             Os archiveOs,

@@ -53,6 +54,7 @@
             String archiveChecksum) {

         super(source,

                 revision,

+                license,

                 description,

                 descUrl,

                 archiveOs,

diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkRepository.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkRepository.java
index 673e43f..4adcb49 100755
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkRepository.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkRepository.java
@@ -46,6 +46,8 @@
 

     /** The revision, an int > 0, for all packages (platform, add-on, tool, doc). */

     public static final String NODE_REVISION    = "revision";                   //$NON-NLS-1$

+    /** The optional license for all packages (platform, add-on, tool, doc) or for a lib. */

+    public static final String NODE_LICENSE = "license";                        //$NON-NLS-1$

     /** The optional description for all packages (platform, add-on, tool, doc) or for a lib. */

     public static final String NODE_DESCRIPTION = "description";                //$NON-NLS-1$

     /** The optional description URL for all packages (platform, add-on, tool, doc). */

diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository.xsd b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository.xsd
index 6aff444..1862ae8 100755
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository.xsd
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository.xsd
@@ -56,6 +56,9 @@
                             <!-- The revision, an int > 0, incremented each time a new
                                  package is generated. -->
                             <xsd:element name="revision"    type="xsd:positiveInteger" />
+                            <!-- The optional license of this package. If present, users will have
+                                 to agree to it before downloading. -->
+                            <xsd:element name="license" type="xsd:string" minOccurs="0" />
                             <!-- The optional description of this package. -->
                             <xsd:element name="description" type="xsd:string" minOccurs="0" />
                             <!-- The optional description URL of this package -->
@@ -84,6 +87,9 @@
                             <!-- The revision, an int > 0, incremented each time a new
                                  package is generated. -->
                             <xsd:element name="revision"    type="xsd:positiveInteger" />
+                            <!-- The optional license of this package. If present, users will have
+                                 to agree to it before downloading. -->
+                            <xsd:element name="license" type="xsd:string" minOccurs="0" />
                             <!-- The optional description of this package. -->
                             <xsd:element name="description" type="xsd:string" minOccurs="0" />
                             <!-- The optional description URL of this package -->
@@ -124,6 +130,9 @@
                             <!-- The revision, an int > 0, incremented each time a new
                                  package is generated. -->
                             <xsd:element name="revision"    type="xsd:positiveInteger" />
+                            <!-- The optional license of this package. If present, users will have
+                                 to agree to it before downloading. -->
+                            <xsd:element name="license" type="xsd:string" minOccurs="0" />
                             <!-- The optional description of this package. -->
                             <xsd:element name="description" type="xsd:string" minOccurs="0" />
                             <!-- The optional description URL of this package -->
@@ -148,6 +157,9 @@
                             <!-- The revision, an int > 0, incremented each time a new
                                  package is generated. -->
                             <xsd:element name="revision"    type="xsd:positiveInteger" />
+                            <!-- The optional license of this package. If present, users will have
+                                 to agree to it before downloading. -->
+                            <xsd:element name="license" type="xsd:string" minOccurs="0" />
                             <!-- The optional description of this package. -->
                             <xsd:element name="description" type="xsd:string" minOccurs="0" />
                             <!-- The optional description URL of this package -->
diff --git a/tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/repository/repository_sample.xml b/tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/repository/repository_sample.xml
index fb6283d..a2d0cda 100755
--- a/tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/repository/repository_sample.xml
+++ b/tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/repository/repository_sample.xml
@@ -26,6 +26,8 @@
         <sdk:api-level>1</sdk:api-level>

         <sdk:revision>3</sdk:revision>

         <sdk:description>Some optional description</sdk:description>

+        <sdk:license>This is the license

+        for this platform.</sdk:license>

         <sdk:desc-url>http://www.example.com/platform1.html</sdk:desc-url>

         <!-- The archives node is mandatory and it cannot be empty. -->

         <sdk:archives>

@@ -41,6 +43,7 @@
         <sdk:api-level>1</sdk:api-level>

         <sdk:revision>1</sdk:revision>

         <sdk:description>Some optional description</sdk:description>

+        <!-- the license element is not mandatory. -->

         <sdk:desc-url>http://www.example.com/docs.html</sdk:desc-url>

         <sdk:archives>

             <sdk:archive os="any">

@@ -56,6 +59,8 @@
         <sdk:api-level>1</sdk:api-level>

         <sdk:vendor>John Doe</sdk:vendor>

         <sdk:revision>1</sdk:revision>

+        <!-- license can be empty. -->

+        <sdk:license></sdk:license>

         <sdk:description>Some optional description</sdk:description>

         <sdk:desc-url>http://www.example.com/myfirstaddon</sdk:desc-url>

         <sdk:archives>

@@ -82,6 +87,7 @@
         <sdk:version>1.1</sdk:version>

         <sdk:api-level>2</sdk:api-level>

         <sdk:revision>12</sdk:revision>

+        <sdk:license>This is the license for this package.</sdk:license>

         <!-- sdk:description and sdk:desc-url are optional -->

         <sdk:archives>

             <sdk:archive os="windows">

@@ -139,12 +145,14 @@
                 <sdk:name>com.android.mymaps</sdk:name>

             </sdk:lib>

         </sdk:libs>

+        <sdk:license>This is the license for this package.</sdk:license>

     </sdk:add-on>

 

     <sdk:tool>

         <sdk:revision>1</sdk:revision>

         <sdk:description>Some optional description</sdk:description>

         <sdk:desc-url>http://www.example.com/tools.html</sdk:desc-url>

+        <sdk:license>This is the license for this package.</sdk:license>

         <sdk:archives>

             <sdk:archive os="any">

                 <sdk:size>65536</sdk:size>

@@ -157,6 +165,7 @@
     <sdk:doc>

         <sdk:api-level>2</sdk:api-level>

         <sdk:revision>42</sdk:revision>

+        <sdk:license>This is the license for this package.</sdk:license>

         <sdk:archives>

             <sdk:archive os="windows">

                 <sdk:size>65536</sdk:size>

@@ -178,6 +187,7 @@
 

     <sdk:tool>

         <sdk:revision>42</sdk:revision>

+        <sdk:license>This is the license for this package.</sdk:license>

         <sdk:archives>

             <sdk:archive os="windows">

                 <sdk:size>65536</sdk:size>

@@ -198,6 +208,7 @@
     </sdk:tool>

 

     <sdk:add-on>

+        <sdk:license>This is the license for this package.</sdk:license>

         <sdk:name>This add-on has no libraries</sdk:name>

         <sdk:api-level>4</sdk:api-level>

         <sdk:vendor>Joe Bar</sdk:vendor>

diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterWindowImpl.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterWindowImpl.java
index ddf53f0..ead8d61 100755
--- a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterWindowImpl.java
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterWindowImpl.java
@@ -21,7 +21,6 @@
 import com.android.sdklib.internal.repository.Archive;

 import com.android.sdklib.internal.repository.ITask;

 import com.android.sdklib.internal.repository.ITaskMonitor;

-import com.android.sdklib.internal.repository.Package;

 import com.android.sdklib.internal.repository.RepoSource;

 import com.android.sdklib.repository.SdkRepository;

 

@@ -42,26 +41,16 @@
 import org.eclipse.swt.widgets.List;

 import org.eclipse.swt.widgets.Shell;

 

-import java.io.File;

-import java.io.FileInputStream;

-import java.io.FileOutputStream;

-import java.io.IOException;

 import java.io.InputStream;

 import java.lang.reflect.Constructor;

-import java.net.URL;

-import java.security.MessageDigest;

 import java.util.ArrayList;

 import java.util.Collection;

-import java.util.zip.ZipEntry;

-import java.util.zip.ZipInputStream;

 

 /**

  * This is the private implementation of the UpdateWindow.

  */

 public class UpdaterWindowImpl {

 

-    private static final int NUM_MONITOR_INC = 100;

-

     /** Internal data shared between the window and its pages. */

     private final UpdaterData mUpdaterData = new UpdaterData();

     /** The array of pages instances. Only one is visible at a time. */

@@ -351,7 +340,7 @@
         mTaskFactory.start("Installing Archives", new ITask() {

             public void run(ITaskMonitor monitor) {

 

-                final int progressPerArchive = 2 * NUM_MONITOR_INC + 10;

+                final int progressPerArchive = 2 * Archive.NUM_MONITOR_INC;

                 monitor.setProgressMax(archives.size() * progressPerArchive);

                 monitor.setDescription("Preparing to install archives");

 

@@ -359,34 +348,20 @@
                 for (Archive archive : archives) {

 

                     int nextProgress = monitor.getProgress() + progressPerArchive;

-                    File archiveFile = null;

                     try {

                         if (monitor.isCancelRequested()) {

                             break;

                         }

 

-                        String name = archive.getParentPackage().getShortDescription();

-

-                        // TODO: we should not see this test fail if we had the filter UI above.

-                        if (!archive.isCompatible()) {

-                            monitor.setResult("Skipping incompatible archive: %1$s", name);

-                            continue;

+                        if (archive.install(mUpdaterData.getOsSdkRoot(), monitor)) {

+                            numInstalled++;

                         }

 

-                        archiveFile = downloadArchive(archive, monitor);

-                        if (archiveFile != null) {

-                            if (installArchive(archive, archiveFile, monitor)) {

-                                monitor.setResult("Installed: %1$s", name);

-                                numInstalled++;

-                            }

-                        }

                     } catch (Throwable t) {

                         // Display anything unexpected in the monitor.

                         monitor.setResult("Unexpected Error: %1$s", t.getMessage());

 

                     } finally {

-                        // Delete the temp archive if it exists

-                        deleteFileOrFolder(archiveFile);

 

                         // Always move the progress bar to the desired position.

                         // This allows internal methods to not have to care in case

@@ -406,417 +381,6 @@
         });

     }

 

-    /**

-     * Downloads an archive and returns the temp file with it.

-     * Caller is responsible with deleting the temp file when done.

-     */

-    private File downloadArchive(Archive archive, ITaskMonitor monitor) {

-

-        File tmpFileToDelete = null;

-        try {

-            File tmpFile = File.createTempFile("sdkupload", ".bin"); //$NON-NLS-1$ //$NON-NLS-2$

-            tmpFileToDelete = tmpFile;

-

-            String name = archive.getParentPackage().getShortDescription();

-            String desc = String.format("Downloading %1$s", name);

-            monitor.setDescription(desc);

-

-            String link = archive.getUrl();

-            if (!link.startsWith("http://")                          //$NON-NLS-1$

-                    && !link.startsWith("https://")                  //$NON-NLS-1$

-                    && !link.startsWith("ftp://")) {                 //$NON-NLS-1$

-                // Make the URL absolute by prepending the source

-                Package pkg = archive.getParentPackage();

-                RepoSource src = pkg.getParentSource();

-                if (src == null) {

-                    monitor.setResult("Internal error: no source for archive %1$s", name);

-                    return null;

-                }

-

-                // take the URL to the repository.xml and remove the last component

-                // to get the base

-                String repoXml = src.getUrl();

-                int pos = repoXml.lastIndexOf('/');

-                String base = repoXml.substring(0, pos + 1);

-

-                link = base + link;

-            }

-

-            if (fetchUrl(tmpFile, archive, link, desc, monitor)) {

-                // Fetching was successful, don't delete the temp file here!

-                tmpFileToDelete = null;

-                return tmpFile;

-            }

-

-        } catch (IOException e) {

-            monitor.setResult(e.getMessage());

-

-        } finally {

-            deleteFileOrFolder(tmpFileToDelete);

-        }

-        return null;

-    }

-

-    /**

-     * Actually performs the download.

-     * Also computes the SHA1 of the file on the fly.

-     * <p/>

-     * Success is defined as downloading as many bytes as was expected and having the same

-     * SHA1 as expected. Returns true on success or false if any of those checks fail.

-     * <p/>

-     * Increments the monitor by {@link #NUM_MONITOR_INC}.

-     */

-    private boolean fetchUrl(File tmpFile,

-            Archive archive,

-            String urlString,

-            String description,

-            ITaskMonitor monitor) {

-        URL url;

-

-        description += " (%1$d%%, %2$.0f KiB/s, %3$d %4$s left)";

-

-        FileOutputStream os = null;

-        InputStream is = null;

-        try {

-            url = new URL(urlString);

-            is = url.openStream();

-            os = new FileOutputStream(tmpFile);

-

-            MessageDigest digester = archive.getChecksumType().getMessageDigest();

-

-            byte[] buf = new byte[65536];

-            int n;

-

-            long total = 0;

-            long size = archive.getSize();

-            long inc = size / NUM_MONITOR_INC;

-            long next_inc = inc;

-

-            long startMs = System.currentTimeMillis();

-            long nextMs = startMs + 2000;  // start update after 2 seconds

-

-            while ((n = is.read(buf)) >= 0) {

-                if (n > 0) {

-                    os.write(buf, 0, n);

-                    digester.update(buf, 0, n);

-                }

-

-                long timeMs = System.currentTimeMillis();

-

-                total += n;

-                if (total >= next_inc) {

-                    monitor.incProgress(1);

-                    next_inc += inc;

-                }

-

-                if (timeMs > nextMs) {

-                    long delta = timeMs - startMs;

-                    if (total > 0 && delta > 0) {

-                        // percent left to download

-                        int percent = (int) (100 * total / size);

-                        // speed in KiB/s

-                        float speed = (float)total / (float)delta * (1000.f / 1024.f);

-                        // time left to download the rest at the current KiB/s rate

-                        int timeLeft = (speed > 1e-3) ?

-                                               (int)(((size - total) / 1024.0f) / speed) :

-                                               0;

-                        String timeUnit = "seconds";

-                        if (timeLeft > 120) {

-                            timeUnit = "minutes";

-                            timeLeft /= 60;

-                        }

-

-                        monitor.setDescription(description, percent, speed, timeLeft, timeUnit);

-                    }

-                    nextMs = timeMs + 1000;  // update every second

-                }

-

-                if (monitor.isCancelRequested()) {

-                    monitor.setResult("Download aborted by user at %1$d bytes.", total);

-                    return false;

-                }

-

-            }

-

-            if (total != size) {

-                monitor.setResult("Download finished with wrong size. Expected %1$d bytes, got %2$d bytes.",

-                        size, total);

-                return false;

-            }

-

-            // Create an hex string from the digest

-            byte[] digest = digester.digest();

-            n = digest.length;

-            String hex = "0123456789abcdef";                     //$NON-NLS-1$

-            char[] hexDigest = new char[n * 2];

-            for (int i = 0; i < n; i++) {

-                int b = digest[i] & 0x0FF;

-                hexDigest[i*2 + 0] = hex.charAt(b >>> 4);

-                hexDigest[i*2 + 1] = hex.charAt(b & 0x0f);

-            }

-

-            String expected = archive.getChecksum();

-            String actual   = new String(hexDigest);

-            if (!actual.equalsIgnoreCase(expected)) {

-                monitor.setResult("Download finished with wrong checksum. Expected %1$s, got %2$s.",

-                        expected, actual);

-                return false;

-            }

-

-            return true;

-

-        } catch (Exception e) {

-            monitor.setResult(e.getMessage());

-

-        } finally {

-            if (os != null) {

-                try {

-                    os.close();

-                } catch (IOException e) {

-                    // pass

-                }

-            }

-

-            if (is != null) {

-                try {

-                    is.close();

-                } catch (IOException e) {

-                    // pass

-                }

-            }

-        }

-

-        return false;

-    }

-

-    /**

-     * Install the given archive in the given folder.

-     */

-    private boolean installArchive(Archive archive, File archiveFile, ITaskMonitor monitor) {

-        String name = archive.getParentPackage().getShortDescription();

-        String desc = String.format("Installing %1$s", name);

-        monitor.setDescription(desc);

-

-        File destFolder = archive.getParentPackage().getInstallFolder(mUpdaterData.getOsSdkRoot());

-

-        File unzipDestFolder = destFolder;

-        File renamedDestFolder = null;

-

-        try {

-            // If this folder already exists, unzip in a temporary folder and then move/unlink.

-            if (destFolder.exists()) {

-                // Find a new temp folder that doesn't exist yet

-                unzipDestFolder = findTempFolder(destFolder, "new");  //$NON-NLS-1$

-

-                if (unzipDestFolder == null) {

-                    // this should not seriously happen.

-                    monitor.setResult("Failed to find a suitable temp directory similar to %1$s.",

-                            destFolder.getPath());

-                    return false;

-                }

-

-                if (!unzipDestFolder.mkdirs()) {

-                    monitor.setResult("Failed to create temp directory %1$s",

-                            unzipDestFolder.getPath());

-                    return false;

-                }

-            }

-

-            if (!unzipFolder(archiveFile, archive.getSize(), unzipDestFolder, desc, monitor)) {

-                return false;

-            }

-

-            if (unzipDestFolder != destFolder) {

-                // Swap the old folder by the new one.

-                // Both original folders will be deleted in the finally clause below.

-                renamedDestFolder = findTempFolder(destFolder, "old");  //$NON-NLS-1$

-                if (renamedDestFolder == null) {

-                    // this should not seriously happen.

-                    monitor.setResult("Failed to find a suitable temp directory similar to %1$s.",

-                            destFolder.getPath());

-                    return false;

-                }

-

-                if (!destFolder.renameTo(renamedDestFolder)) {

-                    monitor.setResult("Failed to rename directory %1$s to %2$s",

-                            destFolder.getPath(), renamedDestFolder.getPath());

-                    return false;

-

-                }

-                if (!unzipDestFolder.renameTo(destFolder)) {

-                    monitor.setResult("Failed to rename directory %1$s to %2$s",

-                            unzipDestFolder.getPath(), destFolder.getPath());

-                    return false;

-                }

-            }

-

-            return true;

-

-        } finally {

-            // Cleanup if the unzip folder is still set.

-            deleteFileOrFolder(renamedDestFolder);

-            if (unzipDestFolder != destFolder) {

-                deleteFileOrFolder(unzipDestFolder);

-            }

-        }

-    }

-

-    private boolean unzipFolder(File archiveFile,

-            long compressedSize,

-            File unzipDestFolder,

-            String description,

-            ITaskMonitor monitor) {

-

-        description += " (%1$d%%)";

-

-        FileInputStream fis = null;

-        ZipInputStream  zis = null;

-        try {

-            fis = new FileInputStream(archiveFile);

-            zis = new ZipInputStream(fis);

-

-            // To advance the percent and the progress bar, we don't know the number of

-            // items left to unzip. However we know the size of the archive and the size of

-            // each uncompressed item. The zip file format overhead is negligible so that's

-            // a good approximation.

-            long incStep = compressedSize / NUM_MONITOR_INC;

-            long incTotal = 0;

-            long incCurr = 0;

-            int lastPercent = 0;

-

-            byte[] buf = new byte[65536];

-

-            ZipEntry entry;

-            while ((entry = zis.getNextEntry()) != null) {

-

-                String name = entry.getName();

-

-                // ZipFile entries should have forward slashes, but not all Zip

-                // implementations can be expected to do that.

-                name = name.replace('\\', '/');

-

-                File destFile = new File(unzipDestFolder, name);

-

-                if (name.endsWith("/")) {  //$NON-NLS-1$

-                    // Create directory if it doesn't exist yet. This allows us to create

-                    // empty directories.

-                    if (!destFile.isDirectory() && !destFile.mkdirs()) {

-                        monitor.setResult("Failed to create temp directory %1$s",

-                                destFile.getPath());

-                        return false;

-                    }

-                    continue;

-                } else if (name.indexOf('/') != -1) {

-                    // Otherwise it's a file in a sub-directory.

-                    // Make sure the parent directory has been created.

-                    File parentDir = destFile.getParentFile();

-                    if (!parentDir.isDirectory()) {

-                        if (!parentDir.mkdirs()) {

-                            monitor.setResult("Failed to create temp directory %1$s",

-                                    parentDir.getPath());

-                            return false;

-                        }

-                    }

-                }

-

-                FileOutputStream fos = null;

-                try {

-                    fos = new FileOutputStream(destFile);

-                    int n;

-                    while ((n = zis.read(buf)) != -1) {

-                        if (n > 0) {

-                            fos.write(buf, 0, n);

-                        }

-                    }

-                } finally {

-                    if (fos != null) {

-                        fos.close();

-                    }

-                }

-

-                // Increment progress bar to match. We update only between files.

-                for(incTotal += entry.getCompressedSize(); incCurr < incTotal; incCurr += incStep) {

-                    monitor.incProgress(1);

-                }

-

-                int percent = (int) (100 * incTotal / compressedSize);

-                if (percent != lastPercent) {

-                    monitor.setDescription(description, percent);

-                    lastPercent = percent;

-                }

-

-                if (monitor.isCancelRequested()) {

-                    return false;

-                }

-            }

-

-            return true;

-

-        } catch (IOException e) {

-            monitor.setResult("Unzip failed: %1$s", e.getMessage());

-

-        } finally {

-            if (zis != null) {

-                try {

-                    zis.close();

-                } catch (IOException e) {

-                    // pass

-                }

-            }

-            if (fis != null) {

-                try {

-                    fis.close();

-                } catch (IOException e) {

-                    // pass

-                }

-            }

-        }

-

-        return false;

-    }

-

-    /**

-     * Finds a temp folder which name is similar to the one of the ideal folder

-     * and with a ".tmpN" appended.

-     * <p/>

-     * This operation is not atomic so there's no guarantee the folder can't get

-     * created in between. This is however unlikely and the caller can assume the

-     * returned folder does not exist yet.

-     * <p/>

-     * Returns null if no such folder can be found (e.g. if all candidates exist),

-     * which is rather unlikely.

-     */

-    private File findTempFolder(File idealFolder, String suffix) {

-        String basePath = idealFolder.getPath();

-

-        for (int i = 1; i < 100; i++) {

-            File folder = new File(String.format("%1$s.%2$s%3$02d", basePath, suffix, i));  //$NON-NLS-1$

-            if (!folder.exists()) {

-                return folder;

-            }

-        }

-        return null;

-    }

-

-    /**

-     * Deletes a file or a directory.

-     * Directories are deleted recursively.

-     * The argument can be null.

-     */

-    private void deleteFileOrFolder(File fileOrFolder) {

-        if (fileOrFolder != null) {

-            if (fileOrFolder.isDirectory()) {

-                // Must delete content recursively first

-                for (File item : fileOrFolder.listFiles()) {

-                    deleteFileOrFolder(item);

-                }

-            }

-            if (!fileOrFolder.delete()) {

-                fileOrFolder.deleteOnExit();

-            }

-        }

-    }

-

     // End of hiding from SWT Designer

     //$hide<<$

 }