Merge "Fix build, GsmServiceStateTracker was using old EventLog API."
diff --git a/api/current.xml b/api/current.xml
index 01ff582..689f5dd 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -20208,28 +20208,6 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<method name="getActiveMinimumPasswordLength"
- return="int"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
-<method name="getActivePasswordMode"
- return="int"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
 <method name="getCurrentFailedPasswordAttempts"
  return="int"
  abstract="false"
@@ -20274,6 +20252,17 @@
  visibility="public"
 >
 </method>
+<method name="isActivePasswordSufficient"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="isAdminActive"
  return="boolean"
  abstract="false"
@@ -20287,6 +20276,17 @@
 <parameter name="who" type="android.content.ComponentName">
 </parameter>
 </method>
+<method name="lockNow"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="removeActiveAdmin"
  return="void"
  abstract="false"
@@ -20300,6 +20300,19 @@
 <parameter name="who" type="android.content.ComponentName">
 </parameter>
 </method>
+<method name="resetPassword"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="password" type="java.lang.String">
+</parameter>
+</method>
 <method name="setMaximumTimeToLock"
  return="void"
  abstract="false"
@@ -20395,7 +20408,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="2000"
+ value="3000"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -20406,6 +20419,17 @@
  type="int"
  transient="false"
  volatile="false"
+ value="2000"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="PASSWORD_MODE_SOMETHING"
+ type="int"
+ transient="false"
+ volatile="false"
  value="1000"
  static="true"
  final="true"
@@ -20424,28 +20448,6 @@
  visibility="public"
 >
 </field>
-<field name="WIPE_EXTERNAL_STORAGE"
- type="int"
- transient="false"
- volatile="false"
- value="2"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
-<field name="WIPE_LOW_LEVEL_FORMAT"
- type="int"
- transient="false"
- volatile="false"
- value="1"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
 </class>
 <class name="Dialog"
  extends="java.lang.Object"
@@ -53548,6 +53550,17 @@
 <parameter name="stroke" type="android.gesture.GestureStroke">
 </parameter>
 </method>
+<method name="clone"
+ return="java.lang.Object"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="describeContents"
  return="int"
  abstract="false"
@@ -54737,6 +54750,17 @@
 <parameter name="t" type="long">
 </parameter>
 </constructor>
+<method name="clone"
+ return="java.lang.Object"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <field name="timestamp"
  type="long"
  transient="false"
@@ -55061,6 +55085,17 @@
  visibility="public"
 >
 </method>
+<method name="clone"
+ return="java.lang.Object"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="computeOrientedBoundingBox"
  return="android.gesture.OrientedBoundingBox"
  abstract="false"
diff --git a/core/java/android/app/DevicePolicyManager.java b/core/java/android/app/DevicePolicyManager.java
index 4fdfe0a..538ba5b 100644
--- a/core/java/android/app/DevicePolicyManager.java
+++ b/core/java/android/app/DevicePolicyManager.java
@@ -79,9 +79,10 @@
      * Activity action: have the user enter a new password.  This activity
      * should be launched after using {@link #setPasswordMode(ComponentName, int)}
      * or {@link #setMinimumPasswordLength(ComponentName, int)} to have the
-     * user enter a new password that meets the current requirements.  If the
-     * current password is sufficient, the activity will exit immediately without
-     * being displayed to the user.  Upon receiving a result from this activity,
+     * user enter a new password that meets the current requirements.  You can
+     * use {@link #isActivePasswordSufficient()} to determine whether you need
+     * to have the user select a new password in order to meet the current
+     * constraints.  Upon being resumed from this activity,
      * you can check the new password characteristics to see if they are
      * sufficient.
      */
@@ -122,21 +123,31 @@
     
     /**
      * Constant for {@link #setPasswordMode}: the policy has no requirements
-     * for the password.
+     * for the password.  Note that mode constants are ordered so that higher
+     * values are more restrictive.
      */
     public static final int PASSWORD_MODE_UNSPECIFIED = 0;
     
     /**
-     * Constant for {@link #setPasswordMode}: the user must have at least a
-     * numeric password.
+     * Constant for {@link #setPasswordMode}: the policy requires some kind
+     * of password, but doesn't care what it is.  Note that mode constants
+     * are ordered so that higher values are more restrictive.
      */
-    public static final int PASSWORD_MODE_NUMERIC = 1000;
+    public static final int PASSWORD_MODE_SOMETHING = 1000;
+    
+    /**
+     * Constant for {@link #setPasswordMode}: the user must have at least a
+     * numeric password.  Note that mode constants are ordered so that higher
+     * values are more restrictive.
+     */
+    public static final int PASSWORD_MODE_NUMERIC = 2000;
     
     /**
      * Constant for {@link #setPasswordMode}: the user must have at least an
-     * alphanumeric password.
+     * alphanumeric password.  Note that mode constants are ordered so that higher
+     * values are more restrictive.
      */
-    public static final int PASSWORD_MODE_ALPHANUMERIC = 2000;
+    public static final int PASSWORD_MODE_ALPHANUMERIC = 3000;
     
     /**
      * Called by an application that is administering the device to set the
@@ -147,10 +158,15 @@
      * take place immediately.  To prompt the user for a new password, use
      * {@link #ACTION_SET_NEW_PASSWORD} after setting this value.
      * 
+     * <p>Mode constants are ordered so that higher values are more restrictive;
+     * thus the highest requested mode constant (between the policy set here,
+     * the user's preference, and any other considerations) is the one that
+     * is in effect.
+     * 
      * @param admin Which {@link DeviceAdmin} this request is associated with.
      * @param mode The new desired mode.  One of
-     * {@link #PASSWORD_MODE_UNSPECIFIED}, {@link #PASSWORD_MODE_NUMERIC},
-     * or {@link #PASSWORD_MODE_ALPHANUMERIC}.
+     * {@link #PASSWORD_MODE_UNSPECIFIED}, {@link #PASSWORD_MODE_SOMETHING},
+     * {@link #PASSWORD_MODE_NUMERIC}, or {@link #PASSWORD_MODE_ALPHANUMERIC}.
      */
     public void setPasswordMode(ComponentName admin, int mode) {
         if (mService != null) {
@@ -178,21 +194,6 @@
     }
     
     /**
-     * Retrieve the password mode associated with the last password the
-     * user selected.
-     */
-    public int getActivePasswordMode() {
-        if (mService != null) {
-            try {
-                return mService.getActivePasswordMode();
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed talking with device policy service", e);
-            }
-        }
-        return PASSWORD_MODE_UNSPECIFIED;
-    }
-    
-    /**
      * Called by an application that is administering the device to set the
      * minimum allowed password length.  After setting this, the user
      * will not be able to enter a new password that is not at least as
@@ -234,18 +235,22 @@
     }
     
     /**
-     * Retrieve the password length associated with the last password the
-     * user selected.
+     * Determine whether the current password the user has set is sufficient
+     * to meet the policy requirements (mode, minimum length) that have been
+     * requested.
+     * 
+     * @return Returns true if the password meets the current requirements,
+     * else false.
      */
-    public int getActiveMinimumPasswordLength() {
+    public boolean isActivePasswordSufficient() {
         if (mService != null) {
             try {
-                return mService.getActiveMinimumPasswordLength();
+                return mService.isActivePasswordSufficient();
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed talking with device policy service", e);
             }
         }
-        return 0;
+        return false;
     }
     
     /**
@@ -262,6 +267,30 @@
         }
         return -1;
     }
+
+    /**
+     * Force a new password on the user.  This takes effect immediately.  The
+     * given password must meet the current password minimum length constraint
+     * or it will be rejected.  The given password will be accepted regardless
+     * of the current password mode, automatically adjusting the password mode
+     * higher if needed.  (The string you give here is acceptable for any mode;
+     * if it contains only digits, that is still an acceptable alphanumeric
+     * password.)
+     * 
+     * @param password The new password for the user.
+     * @return Returns true if the password was applied, or false if it is
+     * not acceptable for the current constraints.
+     */
+    public boolean resetPassword(String password) {
+        if (mService != null) {
+            try {
+                return mService.resetPassword(password);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed talking with device policy service", e);
+            }
+        }
+        return false;
+    }
     
     /**
      * Called by an application that is administering the device to set the
@@ -298,22 +327,25 @@
     }
     
     /**
-     * Constant for {@link #wipeData}: perform a low-level format of data
-     * storage.
+     * Make the device lock immediately, as if the lock screen timeout has
+     * expired at the point of this call.
      */
-    public static final int WIPE_LOW_LEVEL_FORMAT = 0x0001;
-    
-    /**
-     * Constant for {@link #wipeData}: also wipe any external storage.
-     */
-    public static final int WIPE_EXTERNAL_STORAGE = 0x0002;
+    public void lockNow() {
+        if (mService != null) {
+            try {
+                mService.lockNow();
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed talking with device policy service", e);
+            }
+        }
+    }
     
     /**
      * Ask the user date be wiped.  This will cause the device to reboot,
-     * erasing all user data while next booting up.
+     * erasing all user data while next booting up.  External storage such
+     * as SD cards will not be erased.
      * 
-     * @param flags Bit mask of additional options: currently
-     * {@link #WIPE_LOW_LEVEL_FORMAT} and {@link #WIPE_EXTERNAL_STORAGE}.
+     * @param flags Bit mask of additional options: currently must be 0.
      */
     public void wipeData(int flags) {
         if (mService != null) {
diff --git a/core/java/android/app/IDevicePolicyManager.aidl b/core/java/android/app/IDevicePolicyManager.aidl
index f62647f..7e38194 100644
--- a/core/java/android/app/IDevicePolicyManager.aidl
+++ b/core/java/android/app/IDevicePolicyManager.aidl
@@ -26,17 +26,20 @@
 interface IDevicePolicyManager {
     void setPasswordMode(in ComponentName who, int mode);
     int getPasswordMode();
-    int getActivePasswordMode();
     
     void setMinimumPasswordLength(in ComponentName who, int length);
     int getMinimumPasswordLength();
-    int getActiveMinimumPasswordLength();
     
+    boolean isActivePasswordSufficient();
     int getCurrentFailedPasswordAttempts();
     
+    boolean resetPassword(String password);
+    
     void setMaximumTimeToLock(in ComponentName who, long timeMs);
     long getMaximumTimeToLock();
     
+    void lockNow();
+    
     void wipeData(int flags);
     
     void setActiveAdmin(in ComponentName policyReceiver);
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index 23762ca..b5408ae 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -28,6 +28,7 @@
     void setPokeLock(int pokey, IBinder lock, String tag);
     int getSupportedWakeLockFlags();
     void setStayOnSetting(int val);
+    void setMaximumScreenOffTimeount(int timeMs);
     void preventScreenOn(boolean prevent);
     boolean isScreenOn();
     void reboot(String reason);
diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java
index eb863ef..6bf09b5 100644
--- a/core/java/android/provider/Downloads.java
+++ b/core/java/android/provider/Downloads.java
@@ -17,6 +17,11 @@
 package android.provider;
 
 import android.net.Uri;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+
+import java.io.File;
 
 /**
  * The Download Manager
@@ -1065,5 +1070,27 @@
          */
         public static final int VISIBILITY_HIDDEN = 2;
 
+        /**
+         * Using a file name, create a title for a download.  Then store it in
+         * the database and return it.
+         *
+         * @param context Context for reaching the {@link ContentResolver} so
+         *      the database can be updated with the new title.
+         * @param filename Full path to the file.  Used to generate a title.
+         * @param id Id of the download, so the new title can be stored in the
+         *      database
+         * @return String Newly created title.
+         * @hide
+         */
+        public static String createTitleFromFilename(Context context,
+                String filename, long id) {
+            if (filename == null) return null;
+            String title = new File(filename).getName();
+            ContentValues values = new ContentValues();
+            values.put(COLUMN_TITLE, title);
+            context.getContentResolver().update(ContentUris.withAppendedId(
+                    CONTENT_URI, id), values, null, null);
+            return title;
+        }
     }
 }
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 9f7a370..aafe453 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -32,6 +32,7 @@
 import java.io.IOException;
 import java.io.RandomAccessFile;
 import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
 import java.util.Arrays;
 import java.util.List;
 
@@ -79,9 +80,9 @@
      * pin = digit-only password
      * password = alphanumeric password
      */
-    public static final int MODE_PATTERN = 0;
-    public static final int MODE_PIN = 1;
-    public static final int MODE_PASSWORD = 2;
+    public static final int MODE_PATTERN = DevicePolicyManager.PASSWORD_MODE_SOMETHING;
+    public static final int MODE_PIN = DevicePolicyManager.PASSWORD_MODE_NUMERIC;
+    public static final int MODE_PASSWORD = DevicePolicyManager.PASSWORD_MODE_ALPHANUMERIC;
 
     /**
      * The minimum number of dots the user must include in a wrong pattern
@@ -94,7 +95,9 @@
     private final static String LOCKOUT_ATTEMPT_DEADLINE = "lockscreen.lockoutattemptdeadline";
     private final static String PATTERN_EVER_CHOSEN_KEY = "lockscreen.patterneverchosen";
     public final static String PASSWORD_TYPE_KEY = "lockscreen.password_type";
+    private final static String LOCK_PASSWORD_SALT_KEY = "lockscreen.password_salt";
 
+    private final Context mContext;
     private final ContentResolver mContentResolver;
     private DevicePolicyManager mDevicePolicyManager;
     private static String sLockPatternFilename;
@@ -104,6 +107,7 @@
      * @param contentResolver Used to look up and save settings.
      */
     public LockPatternUtils(Context context) {
+        mContext = context;
         mContentResolver = context.getContentResolver();
         mDevicePolicyManager =
                 (DevicePolicyManager)context.getSystemService(Context.DEVICE_POLICY_SERVICE);
@@ -126,10 +130,6 @@
         return mDevicePolicyManager.getMinimumPasswordLength();
     }
 
-    public int getActiveMinimumPasswordLength() {
-        return mDevicePolicyManager.getActiveMinimumPasswordLength();
-    }
-
     /**
      * Gets the device policy password mode. If the mode is non-specific, returns
      * MODE_PATTERN which allows the user to choose anything.
@@ -154,10 +154,6 @@
      *
      * @return
      */
-    public int getActivePasswordMode() {
-        return mDevicePolicyManager.getActivePasswordMode();
-    }
-
     public void reportFailedPasswordAttempt() {
         mDevicePolicyManager.reportFailedPasswordAttempt();
     }
@@ -224,7 +220,7 @@
                 return true;
             }
             // Compare the hash from the file with the entered password's hash
-            return Arrays.equals(stored, LockPatternUtils.passwordToHash(password));
+            return Arrays.equals(stored, passwordToHash(password));
         } catch (FileNotFoundException fnfe) {
             return true;
         } catch (IOException ioe) {
@@ -279,6 +275,16 @@
     }
 
     /**
+     * Clear any lock pattern or password.
+     */
+    public void clearLock() {
+        saveLockPassword(null, LockPatternUtils.MODE_PATTERN);
+        setLockPatternEnabled(false);
+        saveLockPattern(null);
+        setLong(PASSWORD_TYPE_KEY, MODE_PATTERN);
+    }
+    
+    /**
      * Save a lock pattern.
      * @param pattern The new pattern to save.
      */
@@ -295,11 +301,13 @@
                 raf.write(hash, 0, hash.length);
             }
             raf.close();
-            setBoolean(PATTERN_EVER_CHOSEN_KEY, true);
-            setLong(PASSWORD_TYPE_KEY, MODE_PATTERN);
-            if (pattern != null && isDevicePolicyActive()) {
-                setActivePasswordState(DevicePolicyManager.PASSWORD_MODE_UNSPECIFIED,
-                        pattern.size());
+            if (pattern != null) {
+                setBoolean(PATTERN_EVER_CHOSEN_KEY, true);
+                setLong(PASSWORD_TYPE_KEY, MODE_PATTERN);
+                DevicePolicyManager dpm = (DevicePolicyManager)mContext.getSystemService(
+                        Context.DEVICE_POLICY_SERVICE);
+                dpm.setActivePasswordState(
+                        DevicePolicyManager.PASSWORD_MODE_SOMETHING, pattern.size());
             }
         } catch (FileNotFoundException fnfe) {
             // Cant do much, unless we want to fail over to using the settings provider
@@ -314,10 +322,9 @@
      * Save a lock password.
      * @param password The password to save
      */
-    public void saveLockPassword(String password) {
+    public void saveLockPassword(String password, int mode) {
         // Compute the hash
-        boolean numericHint = password != null ? TextUtils.isDigitsOnly(password) : false;
-        final byte[] hash  = LockPatternUtils.passwordToHash(password);
+        final byte[] hash = passwordToHash(password);
         try {
             // Write the hash to file
             RandomAccessFile raf = new RandomAccessFile(sLockPasswordFilename, "rw");
@@ -328,10 +335,15 @@
                 raf.write(hash, 0, hash.length);
             }
             raf.close();
-            setLong(PASSWORD_TYPE_KEY, numericHint ? MODE_PIN : MODE_PASSWORD);
-            if (password != null && isDevicePolicyActive()) {
-                setActivePasswordState(numericHint ? DevicePolicyManager.PASSWORD_MODE_NUMERIC
-                    : DevicePolicyManager.PASSWORD_MODE_ALPHANUMERIC, password.length());
+            if (password != null) {
+                int textMode = TextUtils.isDigitsOnly(password) ? MODE_PIN : MODE_PASSWORD;
+                if (textMode > mode) {
+                    mode = textMode;
+                }
+                setLong(PASSWORD_TYPE_KEY, mode);
+                DevicePolicyManager dpm = (DevicePolicyManager)mContext.getSystemService(
+                        Context.DEVICE_POLICY_SERVICE);
+                dpm.setActivePasswordState(mode, password.length());
             }
         } catch (FileNotFoundException fnfe) {
             // Cant do much, unless we want to fail over to using the settings provider
@@ -408,6 +420,21 @@
         }
     }
 
+    private String getSalt() {
+        long salt = getLong(LOCK_PASSWORD_SALT_KEY, 0);
+        if (salt == 0) {
+            try {
+                salt = SecureRandom.getInstance("SHA1PRNG").nextLong();
+                setLong(LOCK_PASSWORD_SALT_KEY, salt);
+                Log.v(TAG, "Initialized lock password salt");
+            } catch (NoSuchAlgorithmException e) {
+                // Throw an exception rather than storing a password we'll never be able to recover
+                throw new IllegalStateException("Couldn't get SecureRandom number", e);
+            }
+        }
+        return Long.toHexString(salt);
+    }
+
     /*
      * Generate a hash for the given password. To avoid brute force attacks, we use a salted hash.
      * Not the most secure, but it is at least a second level of protection. First level is that
@@ -415,15 +442,14 @@
      * @param password the gesture pattern.
      * @return the hash of the pattern in a byte array.
      */
-     public static byte[] passwordToHash(String password) {
+     public byte[] passwordToHash(String password) {
         if (password == null) {
             return null;
         }
         String algo = null;
         byte[] hashed = null;
         try {
-            long salt = 0x2374868151054924L; // TODO: make this unique to device
-            byte[] saltedPassword = (password + Long.toString(salt)).getBytes();
+            byte[] saltedPassword = (password + getSalt()).getBytes();
             byte[] sha1 = MessageDigest.getInstance(algo = "SHA-1").digest(saltedPassword);
             byte[] md5 = MessageDigest.getInstance(algo = "MD5").digest(saltedPassword);
             hashed = (toHex(sha1) + toHex(md5)).getBytes();
@@ -554,6 +580,7 @@
     }
 
     private boolean getBoolean(String systemSettingKey) {
+        // STOPSHIP: these need to be moved to secure settings!
         return 1 ==
                 android.provider.Settings.System.getInt(
                         mContentResolver,
@@ -561,6 +588,7 @@
     }
 
     private void setBoolean(String systemSettingKey, boolean enabled) {
+        // STOPSHIP: these need to be moved to secure settings!
         android.provider.Settings.System.putInt(
                         mContentResolver,
                         systemSettingKey,
@@ -568,10 +596,12 @@
     }
 
     private long getLong(String systemSettingKey, long def) {
+        // STOPSHIP: these need to be moved to secure settings!
         return android.provider.Settings.System.getLong(mContentResolver, systemSettingKey, def);
     }
 
     private void setLong(String systemSettingKey, long value) {
+        // STOPSHIP: these need to be moved to secure settings!
         android.provider.Settings.System.putLong(mContentResolver, systemSettingKey, value);
     }
 
diff --git a/docs/html/resources/community-groups.jd b/docs/html/resources/community-groups.jd
index 61fbcc8..a36a425 100644
--- a/docs/html/resources/community-groups.jd
+++ b/docs/html/resources/community-groups.jd
@@ -1,43 +1,65 @@
 community=true
-page.title=Android Developer Groups
+page.title=Developer Forums
 @jd:body
 
-<p>Welcome to the Android developers community! We're glad you're here and invite you to participate in these discussions. Before posting, please read the <a href="http://source.android.com/discuss/android-discussion-groups-charter">Groups Charter</a> that covers the community guidelines.</p>
+<p>Welcome to the Android developers community! We're glad you're here and invite you to participate in discussions with other Android application developers on topics that interest you.</p>
 
-<p class="note"><strong>Note:</strong> If you are seeking discussion about Android source code (not application development),
-then please refer to the <a href="http://source.android.com/discuss">Open Source Project Mailing lists</a>.</p>
+<p>The lists on this page are primarily for discussion about Android application development. If you are seeking discussion about Android source code (not application development), then please refer to the <a href="http://source.android.com/discuss">Open Source Project Mailing lists</a>.</p>
 
 <p style="margin-bottom:.5em"><strong>Contents</strong></p>
 <ol class="toc">
-  <li><a href="#BeforeYouPost">Before you post</a></li>
-  <li><a href="#ApplicationDeveloperLists">Application developer mailing lists</a></li>
-  <li><a href="#UsingEmail">Using email with the mailing lists</a></li>
+  <li><a href="#StackOverflow">Stack Overflow</a> <span class="new">new!</span></li>
+  <li><a href="#MailingLists">Mailing lists</a><ol>
+    <li><a href="#BeforeYouPost">Before you post</a></li>
+    <li><a href="#UsingEmail">Using email with the mailing lists</a></li>
+    <li><a href="#ApplicationDeveloperLists">Application developer mailing lists</a></li>
+  </ol></li>
+  <li><a href="#MarketHelp">Android Market Help Forum</a></li>
 </ol>
 
-<h2 id="BeforeYouPost">Before you post</h2>
+
+<h2 id="StackOverflow">Stack Overflow</h2>
+
+<p><a href="http://stackoverflow.com">Stack Overflow</a> is a collaboratively edited question and answer site for programmers. It's a great place to ask technical questions about developing and maintaining Android applications. The site is especially useful for asking questions with definite answers, but can also be used for discussing best practices.</p>
+
+<p>On the site, questions and answers relating to Android use the <a href="http://stackoverflow.com/questions/tagged/android">'android' tag</a>. You can look for Android topics by adding '<code>[android]</code>' to your search query, or by visiting the tag page at:</p>
+
+<p style="margin-left: 2em"><a href="http://stackoverflow.com/questions/tagged/android">http://stackoverflow.com/questions/tagged/android</a></p>
+
+<p>If you want to ask a question on Stack Overflow, you can use <a href="http://stackoverflow.com/questions/ask">this form</a>. Before submitting the form, make sure to add the 'android' tag so that other Android developers will be able to find your question. As always, before submitting a new question, take a look at the existing topics to see whether another developer has already asked or answered the question.</p>
+
+<p>If you are getting started with Android development, Stack Overflow may be a great location to ask questions about general Java programming or setting up the Eclipse development environment. Simply tag your questions with the <a href="http://stackoverflow.com/questions/tagged/java">Java</a> or <a href="http://stackoverflow.com/questions/tagged/eclipse">Eclipse</a> tags in these cases.</p>
+
+
+<h2 id="MailingLists">Mailing lists</h2>
+
+<p>There are a number of mailing lists, powered by <a href="http://groups.google.com">Google Groups</a>, available for discussing Android application development.</p>
+
+
+<h3 id="BeforeYouPost">Before you post</h3>
 <p>Before writing a post, please try the following:</p>
 
 <ol>
-<li><a href="{@docRoot}resources/faq/index.html">Read the FAQs</a> The most common questions about developing Android applications are addressed in this frequently updated list.</li>
-<li><strong>Type in keywords of your questions in the main Android site's search bar</strong> (such as the one above). This search encompasses all previous discussions, across all groups, as well as the full contents of the site, documentation, and blogs. Chances are good that somebody has run into the same issue before.</li>
-<li><b>Search the mailing list archives</b> to see whether your questions have already been discussed.
+
+<li>Look through the support information available in the 'More' section of this tab. You may find the answer to your question in the <a href="{@docRoot}resources/faq/commontasks.html">Common Tasks</a>, <a href="{@docRoot}resources/faq/troubleshooting.html">Troubleshooting Tips</a>, or <a href="{@docRoot}resources/faq/index.html">FAQs</a> sections.</li>
+<li>Type in keywords of your questions in the main Android site's search bar (such as the one above). This search encompasses all previous discussions, across all groups, as well as the full contents of the site, documentation, and blogs. Chances are good that somebody has run into the same issue before.</li>
   </li>
 </ol>
 
 <p>If you can't find your answer, then we encourage you to address the community.
 As you write your post, please do the following:
 <ol>
-<li><b>Read
-the <a href="http://sites.google.com/a/android.com/opensource/discuss/android-discussion-groups-charter">mailing list charter</a></b> that covers the community guidelines. 
+<li><strong>Read
+the <a href="http://source.android.com/discuss/android-discussion-groups-charter">mailing list charter</a></strong> that covers the community guidelines.
 </li>
-<li><b>Select the most appropriate mailing list for your question</b>. There are several different lists for 
+<li><strong>Select the most appropriate mailing list for your question</strong>. There are several different lists for
 developers, described below.</li>
 <li>
-    <b>Be very clear</b> about your question
+    <strong>Be very clear</strong> about your question
 in the subject -- it helps everyone, both those trying to answer your
 question as well as those who may be looking for information in the
 future.</li>
-<li><b>Give plenty of details</b> in your post to
+<li><strong>Give plenty of details</strong> in your post to
 help others understand your problem. Code or log snippets, as well as
 pointers to screenshots, may also be helpful. For a great guide to
 phrasing your questions, read <a href="http://www.catb.org/%7Eesr/faqs/smart-questions.html">How To Ask Questions The Smart Way</a>.
@@ -45,77 +67,48 @@
 </ol>
 
 
-<h2 id="ApplicationDeveloperLists">Application developer mailing lists</h2>
-<ul>
-<li><b>Android beginners</b> - You're new to Android application development. You want to figure out how to get started with the Android SDK and the basic Android APIs? Start here. This list is open to any discussion around beginner-type questions for developers using the SDK; this is a great way to get up and running with your new application on the Android platform. Ask about getting your development environment set up, get help with the first steps of Android development (your first User Interface, your first permission, your first file on the Android filesystem, your first app on the Android Market...). Be sure to check the archives first before asking new questions. Please avoid advanced subjects, which belong on android-developers, and user questions, which will get a better reception on android-discuss.
-<ul>
-<li>Subscribe using Google Groups:&nbsp;<a href="http://groups.google.com/group/android-beginners">android-beginners</a></li>
-<li>Subscribe via email:&nbsp;<a href="mailto:android-beginners-subscribe@googlegroups.com">android-beginners-subscribe@googlegroups.com</a></li>
-</ul>
-</li>
-
-<li><b>Android developers</b> - You're now an experienced Android application developer. You've grasped the basics of Android app development, you're comfortable using the SDK, now you want to move to advanced topics. Get help here with troubleshooting applications, advice on implementation, and strategies for improving your application's performance and user experience. This is the not the right place to discuss user issues (use android-discuss for that) or beginner questions with the Android SDK (use android-beginners for that).
-<ul>
-<li>Subscribe using Google Groups:&nbsp;<a href="http://groups.google.com/group/android-developers">android-developers</a></li>
-<li>Subscribe via email:&nbsp;<a href="mailto:android-developers-subscribe@googlegroups.com">android-developers-subscribe@googlegroups.com</a></li>
-</ul>
-</li>
-
-<li><b>Android discuss</b> - The "water cooler" of Android discussion. You can discuss just about anything Android-related here, ideas for the Android platform, announcements about your applications, discussions about Android devices, community resources... As long as your discussion is related to Android, it's on-topic here. However, if you have a discussion here that could belong on another list, you are probably not reaching all of your target audience here and may want to consider shifting to a more targeted list.
-<ul>
-<li>Subscribe using Google Groups:&nbsp;<a href="http://groups.google.com/group/android-discuss">android-discuss</a></li>
-<li>Subscribe via email:&nbsp;<a href="mailto:android-discuss-subscribe@googlegroups.com">android-discuss-subscribe@googlegroups.com</a></li>
-</ul>
-</li>
-
-<li><b>Android ndk</b> - A place for discussing the Android NDK and topics related to using native code in Android applications. 
-<ul>
-<li>Subscribe using Google Groups:&nbsp;<a href="http://groups.google.com/group/android-ndk">android-ndk</a></li>
-<li>Subscribe via email:&nbsp;<a href="mailto:android-ndk-subscribe@googlegroups.com">android-ndk-subscribe@googlegroups.com</a></li>
-</ul>
-</li>
-
-<li><b>Android security discuss</b> - A place for open discussion on secure development, emerging security concerns, and best practices for and by android developers. Please don't disclose vulnerabilities directly on this list, you'd be putting all Android users at risk.
-<ul>
-<li>Subscribe using Google Groups:&nbsp;<a href="http://groups.google.com/group/android-security-discuss">android-security-discuss</a></li>
-<li>Subscribe via email:&nbsp;<a href="mailto:android-security-discuss@googlegroups.com">android-security-discuss@googlegroups.com</a></li>
-</ul>
-</li>
-
-<li><b>Android security announce</b> - A low-volume group for security-related announcements by the Android Security Team.
-<ul>
-<li>Subscribe using Google Groups:&nbsp;<a href="http://groups.google.com/group/android-security-announce">android-security-announce</a></li>
-<li>Subscribe via email:&nbsp;<a href="mailto:android-security-announce-subscribe@googlegroups.com">android-security-announce-subscribe@googlegroups.com</a></li>
-</ul>
-</li>
-
-<li><b>Android Market Help Forum</b> - A web-based discussion forum where you can ask questions or report issues relating to Android Market.
-<ul>
-<li>URL:&nbsp;<a href="http://www.google.com/support/forum/p/Android+Market?hl=en">http://www.google.com/support/forum/p/Android+Market?hl=en</a></li>
-</ul>
-</li>
-
-</ul>
-
-
-
-<h2 id="UsingEmail">Using email with the mailing lists</h2>
+<h3 id="UsingEmail">Using email with the mailing lists</h3>
 <p>Instead of using the <a href="http://groups.google.com/">Google Groups</a> site, you can use your email client of choice to participate in the mailing lists.</p>
 <p>To subscribe to a group without using the Google Groups site, use the link under "subscribe via email" in the lists above.</p>
 <p>To set up how you receive mailing list postings by email:</p>
 
 <ol><li>Sign into the group via the Google Groups site. For example, for the android-framework group you would visit <a href="http://groups.google.com/group/android-framework">http://groups.google.com/group/android-framework</a>.</li>
-<li>Click "Edit
-my membership" on the right side.</li>
-<li>Under "How do
-you want to read this group?" select one of the email options. </li>
+<li>Click "Edit my membership" on the right side.</li>
+<li>Under "How do you want to read this group?" select one of the email options.</li>
 </ol>
 
 
+<h3 id="ApplicationDeveloperLists">Application developer mailing lists</h3>
+<ul>
+<li><strong><a href="http://groups.google.com/group/android-developers">android-developers</a></strong>
+(<a href="mailto:android-developers-subscribe@googlegroups.com">subscribe via email</a>)<br>
+You're now an experienced Android application developer. You've grasped the basics of Android app development, you're comfortable using the SDK, now you want to move to advanced topics. Get help here with troubleshooting applications, advice on implementation, and strategies for improving your application's performance and user experience. This is the not the right place to discuss user issues (use android-discuss for that) or beginner questions with the Android SDK (use android-beginners for that).
+</li>
+
+<li><strong><a href="http://groups.google.com/group/android-discuss">android-discuss</a></strong>
+(<a href="mailto:android-discuss-subscribe@googlegroups.com">subscribe via email</a>)<br>
+The "water cooler" of Android discussion. You can discuss just about anything Android-related here, ideas for the Android platform, announcements about your applications, discussions about Android devices, community resources... As long as your discussion is related to Android, it's on-topic here. However, if you have a discussion here that could belong on another list, you are probably not reaching all of your target audience here and may want to consider shifting to a more targeted list.
+</li>
+
+<li><strong><a href="http://groups.google.com/group/android-ndk">android-ndk</a></strong>
+(<a href="mailto:android-ndk-subscribe@googlegroups.com">subscribe via email</a>)<br>
+A place for discussing the Android NDK and topics related to using native code in Android applications.
+</li>
+
+<li><strong><a href="http://groups.google.com/group/android-security-discuss">android-security-discuss</a></strong>
+(<a href="mailto:android-security-discuss-subscribe@googlegroups.com">subscribe via email</a>)<br>
+A place for open discussion on secure development, emerging security concerns, and best practices for and by android developers. Please don't disclose vulnerabilities directly on this list, you'd be putting all Android users at risk.
+</li>
+
+<li><strong><a href="http://groups.google.com/group/android-security-announce">android-security-announce</a></strong>
+(<a href="mailto:android-security-announce-subscribe@googlegroups.com">subscribe via email</a>)<br>
+A low-volume group for security-related announcements by the Android Security Team.
+</li>
+</ul>
 
 
+<h2 id="MarketHelp">Android Market Help Forum</h2>
 
+<p>The <a href="http://www.google.com/support/forum/p/Android+Market">Android Market Help Forum</a> is a web-based discussion forum where you can ask questions or report issues relating to Android Market.</p>
 
-
-
-</div>
+<p style="margin-left: 2em"><a href="http://www.google.com/support/forum/p/Android+Market">http://www.google.com/support/forum/p/Android+Market</a></p>
diff --git a/docs/html/resources/resources_toc.cs b/docs/html/resources/resources_toc.cs
index f5c573e..e337e38 100644
--- a/docs/html/resources/resources_toc.cs
+++ b/docs/html/resources/resources_toc.cs
@@ -11,7 +11,7 @@
     </h2>
     <ul>
       <li><a href="<?cs var:toroot ?>resources/community-groups.html">
-            <span class="en">Android Developer Groups</span>
+            <span class="en">Developer Forums</span>
           </a></li>
       <li><a href="<?cs var:toroot ?>resources/community-more.html">
             <span class="en">IRC, Twitter</span>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index 89d75b7..979955c 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -71,7 +71,7 @@
     // database gets upgraded properly. At a minimum, please confirm that 'upgradeVersion'
     // is properly propagated through your change.  Not doing so will result in a loss of user
     // settings.
-    private static final int DATABASE_VERSION = 46;
+    private static final int DATABASE_VERSION = 47;
 
     private Context mContext;
 
@@ -580,6 +580,21 @@
             upgradeVersion = 46;
         }
 
+        if (upgradeVersion == 46) {
+            /*
+             * The password mode constants have changed; reset back to no
+             * password.
+             */
+            db.beginTransaction();
+            try {
+                db.execSQL("DELETE FROM system WHERE name='lockscreen.password_type';");
+                db.setTransactionSuccessful();
+            } finally {
+                db.endTransaction();
+            }
+           upgradeVersion = 47;
+       }
+
 
         if (upgradeVersion != currentVersion) {
             Log.w(TAG, "Got stuck trying to upgrade from version " + upgradeVersion
diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java
index e13ddc8..fd42538 100644
--- a/services/java/com/android/server/DevicePolicyManagerService.java
+++ b/services/java/com/android/server/DevicePolicyManagerService.java
@@ -17,6 +17,7 @@
 package com.android.server;
 
 import com.android.common.FastXmlSerializer;
+import com.android.internal.widget.LockPatternUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -27,11 +28,19 @@
 import android.app.DevicePolicyManager;
 import android.app.IDevicePolicyManager;
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.os.Binder;
+import android.os.IBinder;
+import android.os.IPowerManager;
+import android.os.PowerManager;
+import android.os.RecoverySystem;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.provider.Settings;
 import android.util.Log;
 import android.util.Xml;
 
@@ -49,6 +58,8 @@
     
     private final Context mContext;
 
+    IPowerManager mIPowerManager;
+    
     int mActivePasswordMode = DevicePolicyManager.PASSWORD_MODE_UNSPECIFIED;
     int mActivePasswordLength = 0;
     int mFailedPasswordAttempts = 0;
@@ -75,8 +86,16 @@
         mContext = context;
     }
 
+    private IPowerManager getIPowerManager() {
+        if (mIPowerManager == null) {
+            IBinder b = ServiceManager.getService(Context.POWER_SERVICE);
+            mIPowerManager = IPowerManager.Stub.asInterface(b);
+        }
+        return mIPowerManager;
+    }
+    
     ActiveAdmin getActiveAdminForCallerLocked(ComponentName who) throws SecurityException {
-        if (mActiveAdmin != null && mActiveAdmin.getUid() == Binder.getCallingPid()) {
+        if (mActiveAdmin != null && mActiveAdmin.getUid() == Binder.getCallingUid()) {
             if (who != null) {
                 if (!who.getPackageName().equals(mActiveAdmin.info.getActivityInfo().packageName)
                         || !who.getClassName().equals(mActiveAdmin.info.getActivityInfo().name)) {
@@ -258,6 +277,17 @@
         if (!success) {
             Log.w(TAG, "No valid start tag found in policies file");
         }
+        
+        long timeMs = getMaximumTimeToLock();
+        if (timeMs <= 0) {
+            timeMs = Integer.MAX_VALUE;
+        }
+        try {
+            getIPowerManager().setMaximumScreenOffTimeount((int)timeMs);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failure talking with power manager", e);
+        }
+        
     }
 
     public void systemReady() {
@@ -335,15 +365,6 @@
         }
     }
     
-    public int getActivePasswordMode() {
-        synchronized (this) {
-            // This API can only be called by an active device admin,
-            // so try to retrieve it to check that the caller is one.
-            getActiveAdminForCallerLocked(null);
-            return mActivePasswordMode;
-        }
-    }
-    
     public void setMinimumPasswordLength(ComponentName who, int length) {
         synchronized (this) {
             if (who == null) {
@@ -363,12 +384,13 @@
         }
     }
     
-    public int getActiveMinimumPasswordLength() {
+    public boolean isActivePasswordSufficient() {
         synchronized (this) {
             // This API can only be called by an active device admin,
             // so try to retrieve it to check that the caller is one.
             getActiveAdminForCallerLocked(null);
-            return mActivePasswordLength;
+            return mActivePasswordMode >= getPasswordMode()
+                    && mActivePasswordLength >= getMinimumPasswordLength();
         }
     }
     
@@ -381,6 +403,31 @@
         }
     }
     
+    public boolean resetPassword(String password) {
+        int mode;
+        synchronized (this) {
+            // This API can only be called by an active device admin,
+            // so try to retrieve it to check that the caller is one.
+            getActiveAdminForCallerLocked(null);
+            mode = getPasswordMode();
+            if (password.length() < getMinimumPasswordLength()) {
+                return false;
+            }
+        }
+        
+        // Don't do this with the lock held, because it is going to call
+        // back in to the service.
+        long ident = Binder.clearCallingIdentity();
+        try {
+            LockPatternUtils utils = new LockPatternUtils(mContext);
+            utils.saveLockPassword(password, mode);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+        
+        return true;
+    }
+    
     public void setMaximumTimeToLock(ComponentName who, long timeMs) {
         synchronized (this) {
             if (who == null) {
@@ -389,7 +436,21 @@
             ActiveAdmin ap = getActiveAdminForCallerLocked(who);
             if (ap.maximumTimeToUnlock != timeMs) {
                 ap.maximumTimeToUnlock = timeMs;
-                saveSettingsLocked();
+                
+                long ident = Binder.clearCallingIdentity();
+                try {
+                    saveSettingsLocked();
+                    if (timeMs <= 0) {
+                        timeMs = Integer.MAX_VALUE;
+                    }
+                    try {
+                        getIPowerManager().setMaximumScreenOffTimeount((int)timeMs);
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "Failure talking with power manager", e);
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
             }
         }
     }
@@ -400,17 +461,28 @@
         }
     }
     
+    public void lockNow() {
+        synchronized (this) {
+            // This API can only be called by an active device admin,
+            // so try to retrieve it to check that the caller is one.
+            getActiveAdminForCallerLocked(null);
+            // STOPSHIP need to implement.
+        }
+    }
+    
     public void wipeData(int flags) {
         synchronized (this) {
             // This API can only be called by an active device admin,
             // so try to retrieve it to check that the caller is one.
             getActiveAdminForCallerLocked(null);
-            long ident = Binder.clearCallingIdentity();
-            try {
-                Log.w(TAG, "*************** WIPE DATA HERE");
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
+        }
+        long ident = Binder.clearCallingIdentity();
+        try {
+            RecoverySystem.rebootWipeUserData(mContext);
+        } catch (IOException e) {
+            Log.w(TAG, "Failed requesting data wipe", e);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
         }
     }
     
diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java
index bf6996c..f106fc3 100644
--- a/services/java/com/android/server/PowerManagerService.java
+++ b/services/java/com/android/server/PowerManagerService.java
@@ -169,7 +169,8 @@
     private boolean mProximitySensorActive = false;
     private int mProximityPendingValue = -1; // -1 == nothing, 0 == inactive, 1 == active
     private long mLastProximityEventTime;
-    private int mTotalDelaySetting;
+    private int mScreenOffTimeoutSetting;
+    private int mMaximumScreenOffTimeout = Integer.MAX_VALUE;
     private int mKeylightDelay;
     private int mDimDelay;
     private int mScreenOffDelay;
@@ -378,6 +379,16 @@
                 Settings.System.STAY_ON_WHILE_PLUGGED_IN, val);
     }
 
+    public void setMaximumScreenOffTimeount(int timeMs) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.WRITE_SECURE_SETTINGS, null);
+        synchronized (mLocks) {
+            mMaximumScreenOffTimeout = timeMs;
+            // recalculate everything
+            setScreenOffTimeoutsLocked();
+        }
+    }
+
     private class SettingsObserver implements Observer {
         private int getInt(String name) {
             return mSettings.getValues(name).getAsInteger(Settings.System.VALUE);
@@ -390,7 +401,7 @@
                 updateWakeLockLocked();
 
                 // SCREEN_OFF_TIMEOUT
-                mTotalDelaySetting = getInt(SCREEN_OFF_TIMEOUT);
+                mScreenOffTimeoutSetting = getInt(SCREEN_OFF_TIMEOUT);
 
                  // DIM_SCREEN
                 //mDimScreen = getInt(DIM_SCREEN) != 0;
@@ -935,7 +946,8 @@
         pw.println("  mPreventScreenOn=" + mPreventScreenOn
                 + "  mScreenBrightnessOverride=" + mScreenBrightnessOverride
                 + "  mButtonBrightnessOverride=" + mButtonBrightnessOverride);
-        pw.println("  mTotalDelaySetting=" + mTotalDelaySetting);
+        pw.println("  mScreenOffTimeoutSetting=" + mScreenOffTimeoutSetting
+                + " mMaximumScreenOffTimeout=" + mMaximumScreenOffTimeout);
         pw.println("  mLastScreenOnTime=" + mLastScreenOnTime);
         pw.println("  mBroadcastWakeLock=" + mBroadcastWakeLock);
         pw.println("  mStayOnWhilePluggedInScreenDimLock=" + mStayOnWhilePluggedInScreenDimLock);
@@ -2285,7 +2297,10 @@
             mDimDelay = -1;
             mScreenOffDelay = 0;
         } else {
-            int totalDelay = mTotalDelaySetting;
+            int totalDelay = mScreenOffTimeoutSetting;
+            if (totalDelay > mMaximumScreenOffTimeout) {
+                totalDelay = mMaximumScreenOffTimeout;
+            }
             mKeylightDelay = LONG_KEYLIGHT_DELAY;
             if (totalDelay < 0) {
                 mScreenOffDelay = Integer.MAX_VALUE;
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas.java b/tools/layoutlib/bridge/src/android/graphics/Canvas.java
index 9f4dfd0..c49e11e 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Canvas.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas.java
@@ -784,23 +784,25 @@
 
     private final void doDrawRect(int left, int top, int width, int height, Paint paint) {
         // get current graphisc
-        Graphics2D g = getGraphics2d();
+        if (width != 0 && height != 0) {
+            Graphics2D g = getGraphics2d();
 
-        g = getNewGraphics(paint, g);
+            g = getNewGraphics(paint, g);
 
-        Style style = paint.getStyle();
+            Style style = paint.getStyle();
 
-        // draw
-        if (style == Style.FILL || style == Style.FILL_AND_STROKE) {
-            g.fillRect(left, top, width, height);
+            // draw
+            if (style == Style.FILL || style == Style.FILL_AND_STROKE) {
+                g.fillRect(left, top, width, height);
+            }
+
+            if (style == Style.STROKE || style == Style.FILL_AND_STROKE) {
+                g.drawRect(left, top, width, height);
+            }
+
+            // dispose Graphics2D object
+            g.dispose();
         }
-
-        if (style == Style.STROKE || style == Style.FILL_AND_STROKE) {
-            g.drawRect(left, top, width, height);
-        }
-
-        // dispose Graphics2D object
-        g.dispose();
     }
 
     /* (non-Javadoc)
@@ -809,29 +811,31 @@
     @Override
     public void drawRoundRect(RectF rect, float rx, float ry, Paint paint) {
         // get current graphisc
-        Graphics2D g = getGraphics2d();
+        if (rect.width() != 0 && rect.height() != 0) {
+            Graphics2D g = getGraphics2d();
 
-        g = getNewGraphics(paint, g);
+            g = getNewGraphics(paint, g);
 
-        Style style = paint.getStyle();
+            Style style = paint.getStyle();
 
-        // draw
+            // draw
 
-        int arcWidth = (int)(rx * 2);
-        int arcHeight = (int)(ry * 2);
+            int arcWidth = (int)(rx * 2);
+            int arcHeight = (int)(ry * 2);
 
-        if (style == Style.FILL || style == Style.FILL_AND_STROKE) {
-            g.fillRoundRect((int)rect.left, (int)rect.top, (int)rect.width(), (int)rect.height(),
-                    arcWidth, arcHeight);
+            if (style == Style.FILL || style == Style.FILL_AND_STROKE) {
+                g.fillRoundRect((int)rect.left, (int)rect.top, (int)rect.width(), (int)rect.height(),
+                        arcWidth, arcHeight);
+            }
+
+            if (style == Style.STROKE || style == Style.FILL_AND_STROKE) {
+                g.drawRoundRect((int)rect.left, (int)rect.top, (int)rect.width(), (int)rect.height(),
+                        arcWidth, arcHeight);
+            }
+
+            // dispose Graphics2D object
+            g.dispose();
         }
-
-        if (style == Style.STROKE || style == Style.FILL_AND_STROKE) {
-            g.drawRoundRect((int)rect.left, (int)rect.top, (int)rect.width(), (int)rect.height(),
-                    arcWidth, arcHeight);
-        }
-
-        // dispose Graphics2D object
-        g.dispose();
     }
 
 
diff --git a/tools/layoutlib/bridge/src/android/graphics/LinearGradient.java b/tools/layoutlib/bridge/src/android/graphics/LinearGradient.java
index 7cb8f26..38ffed3 100644
--- a/tools/layoutlib/bridge/src/android/graphics/LinearGradient.java
+++ b/tools/layoutlib/bridge/src/android/graphics/LinearGradient.java
@@ -16,13 +16,19 @@
 
 package android.graphics;
 
-import java.awt.GradientPaint;
-import java.awt.Color;
 import java.awt.Paint;
+import java.awt.PaintContext;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.Raster;
 
 public class LinearGradient extends Shader {
 
-    private GradientPaint mGradientPaint;
+    private Paint mJavaPaint;
 
     /**
      * Create a shader that draws a linear gradient along a line.
@@ -46,13 +52,17 @@
             throw new IllegalArgumentException("color and position arrays must be of equal length");
         }
 
-        // FIXME implement multi color linear gradient
-        if (colors.length == 2) {
-            // The hasAlpha flag in Color() is only used to enforce alpha to 0xFF if false.
-            // If true the alpha is read from the int.
-            mGradientPaint = new GradientPaint(x0, y0, new Color(colors[0], true /* hasalpha */),
-                    x1, y1, new Color(colors[1], true /* hasalpha */), tile != TileMode.CLAMP);
+        if (positions == null) {
+            float spacing = 1.f / (colors.length - 1);
+            positions = new float[colors.length];
+            positions[0] = 0.f;
+            positions[colors.length-1] = 1.f;
+            for (int i = 1; i < colors.length - 1 ; i++) {
+                positions[i] = spacing * i;
+            }
         }
+
+        mJavaPaint = new MultiPointLinearGradientPaint(x0, y0, x1, y1, colors, positions, tile);
     }
 
     /**
@@ -68,16 +78,236 @@
      */
     public LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1,
             TileMode tile) {
-        // The hasAlpha flag in Color() is only used to enforce alpha to 0xFF if false.
-        // If true the alpha is read from the int.
-        mGradientPaint = new GradientPaint(x0, y0, new Color(color0, true /* hasalpha */), x1, y1,
-                new Color(color1, true /* hasalpha */), tile != TileMode.CLAMP);
+        this(x0, y0, x1, y1, new int[] { color0, color1}, null /*positions*/, tile);
     }
 
     // ---------- Custom Methods
 
     @Override
     public Paint getJavaPaint() {
-        return mGradientPaint;
+        return mJavaPaint;
+    }
+
+    private static class MultiPointLinearGradientPaint implements Paint {
+        private final static int GRADIENT_SIZE = 100;
+
+        private final float mX0;
+        private final float mY0;
+        private final float mDx;
+        private final float mDy;
+        private final float mDSize2;
+        private final int[] mColors;
+        private final float[] mPositions;
+        private final TileMode mTile;
+        private int[] mGradient;
+
+        public MultiPointLinearGradientPaint(float x0, float y0, float x1, float y1, int colors[],
+                float positions[], TileMode tile) {
+                mX0 = x0;
+                mY0 = y0;
+                mDx = x1 - x0;
+                mDy = y1 - y0;
+                mDSize2 = mDx * mDx + mDy * mDy;
+
+                mColors = colors;
+                mPositions = positions;
+                mTile = tile;
+        }
+
+        public PaintContext createContext(ColorModel cm, Rectangle deviceBounds,
+                Rectangle2D userBounds, AffineTransform xform, RenderingHints hints) {
+            prepareColors();
+            return new MultiPointLinearGradientPaintContext(cm, deviceBounds,
+                    userBounds, xform, hints);
+        }
+
+        public int getTransparency() {
+            return TRANSLUCENT;
+        }
+
+        private synchronized void prepareColors() {
+            if (mGradient == null) {
+                // actually create an array with an extra size, so that we can really go
+                // from 0 to SIZE (100%), or currentPos in the loop below will never equal 1.0
+                mGradient = new int[GRADIENT_SIZE+1];
+
+                int prevPos = 0;
+                int nextPos = 1;
+                for (int i  = 0 ; i <= GRADIENT_SIZE ; i++) {
+                    // compute current position
+                    float currentPos = (float)i/GRADIENT_SIZE;
+                    while (currentPos > mPositions[nextPos]) {
+                        prevPos = nextPos++;
+                    }
+
+                    float percent = (currentPos - mPositions[prevPos]) /
+                            (mPositions[nextPos] - mPositions[prevPos]);
+
+                    mGradient[i] = getColor(mColors[prevPos], mColors[nextPos], percent);
+                }
+            }
+        }
+
+        /**
+         * Returns the color between c1, and c2, based on the percent of the distance
+         * between c1 and c2.
+         */
+        private int getColor(int c1, int c2, float percent) {
+            int a = getChannel((c1 >> 24) & 0xFF, (c2 >> 24) & 0xFF, percent);
+            int r = getChannel((c1 >> 16) & 0xFF, (c2 >> 16) & 0xFF, percent);
+            int g = getChannel((c1 >>  8) & 0xFF, (c2 >>  8) & 0xFF, percent);
+            int b = getChannel((c1      ) & 0xFF, (c2      ) & 0xFF, percent);
+            return a << 24 | r << 16 | g << 8 | b;
+        }
+
+        /**
+         * Returns the channel value between 2 values based on the percent of the distance between
+         * the 2 values..
+         */
+        private int getChannel(int c1, int c2, float percent) {
+            return c1 + (int)((percent * (c2-c1)) + .5);
+        }
+
+        private class MultiPointLinearGradientPaintContext implements PaintContext {
+
+            private ColorModel mColorModel;
+            private final Rectangle mDeviceBounds;
+            private final Rectangle2D mUserBounds;
+            private final AffineTransform mXform;
+            private final RenderingHints mHints;
+
+            public MultiPointLinearGradientPaintContext(ColorModel cm, Rectangle deviceBounds,
+                    Rectangle2D userBounds, AffineTransform xform, RenderingHints hints) {
+                mColorModel = cm;
+                // FIXME: so far all this is always the same rect gotten in getRaster with an indentity matrix?
+                mDeviceBounds = deviceBounds;
+                mUserBounds = userBounds;
+                mXform = xform;
+                mHints = hints;
+            }
+
+            public void dispose() {
+            }
+
+            public ColorModel getColorModel() {
+                return mColorModel;
+            }
+
+            public Raster getRaster(int x, int y, int w, int h) {
+                BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
+
+                int[] data = new int[w*h];
+
+                if (mDx == 0) { // vertical gradient
+                    // compute first column and copy to all other columns
+                    int index = 0;
+                    for (int iy = 0 ; iy < h ; iy++) {
+                        int color = getColor(iy + y, mY0, mDy);
+                        for (int ix = 0 ; ix < w ; ix++) {
+                            data[index++] = color;
+                        }
+                    }
+                } else if (mDy == 0) { // horizontal
+                    // compute first line in a tmp array and copy to all lines
+                    int[] line = new int[w];
+                    for (int ix = 0 ; ix < w ; ix++) {
+                        line[ix] = getColor(ix + x, mX0, mDx);
+                    }
+
+                    for (int iy = 0 ; iy < h ; iy++) {
+                        System.arraycopy(line, 0, data, iy*w, line.length);
+                    }
+                } else {
+                    int index = 0;
+                    for (int iy = 0 ; iy < h ; iy++) {
+                        for (int ix = 0 ; ix < w ; ix++) {
+                            data[index++] = getColor(ix + x, iy + y);
+                        }
+                    }
+                }
+
+                image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/);
+
+                return image.getRaster();
+            }
+        }
+
+        /** Returns a color for the easy vertical/horizontal mode */
+        private int getColor(float absPos, float refPos, float refSize) {
+            float pos = (absPos - refPos) / refSize;
+
+            return getIndexFromPos(pos);
+        }
+
+        /**
+         * Returns a color for an arbitrary point.
+         */
+        private int getColor(float x, float y) {
+            // find the x position on the gradient vector.
+            float _x = (mDx*mDy*(y-mY0) + mDy*mDy*mX0 + mDx*mDx*x) / mDSize2;
+            // from it get the position relative to the vector
+            float pos = (float) ((_x - mX0) / mDx);
+
+            return getIndexFromPos(pos);
+        }
+
+        /**
+         * Returns the color based on the position in the gradient.
+         * <var>pos</var> can be anything, even &lt; 0 or &gt; > 1, as the gradient
+         * will use {@link TileMode} value to convert it into a [0,1] value.
+         */
+        private int getIndexFromPos(float pos) {
+            if (pos < 0.f) {
+                switch (mTile) {
+                    case CLAMP:
+                        pos = 0.f;
+                        break;
+                    case REPEAT:
+                        // remove the integer part to stay in the [0,1] range
+                        // careful: this is a negative value, so use ceil instead of floor
+                        pos = pos - (float)Math.ceil(pos);
+                        break;
+                    case MIRROR:
+                        // get the integer and the decimal part
+                        // careful: this is a negative value, so use ceil instead of floor
+                        int intPart = (int)Math.ceil(pos);
+                        pos = pos - intPart;
+                        // 0  -> -1 : mirrored order
+                        // -1 -> -2: normal order
+                        // etc..
+                        // this means if the intpart is even we invert
+                        if ((intPart % 2) == 0) {
+                            pos = 1.f - pos;
+                        }
+                        break;
+                }
+            } else if (pos > 1f) {
+                switch (mTile) {
+                    case CLAMP:
+                        pos = 1.f;
+                        break;
+                    case REPEAT:
+                        // remove the integer part to stay in the [0,1] range
+                        pos = pos - (float)Math.floor(pos);
+                        break;
+                    case MIRROR:
+                        // get the integer and the decimal part
+                        int intPart = (int)Math.floor(pos);
+                        pos = pos - intPart;
+                        // 0 -> 1 : normal order
+                        // 1 -> 2: mirrored
+                        // etc..
+                        // this means if the intpart is odd we invert
+                        if ((intPart % 2) == 1) {
+                            pos = 1.f - pos;
+                        }
+                        break;
+                }
+            }
+
+            int index = (int)((pos * GRADIENT_SIZE) + .5);
+
+            return mGradient[index];
+        }
     }
 }