auto import from //depot/cupcake/@137055
diff --git a/icu/src/main/native/DecimalFormatInterface.cpp b/icu/src/main/native/DecimalFormatInterface.cpp
index fb5cf9f..6221826 100644
--- a/icu/src/main/native/DecimalFormatInterface.cpp
+++ b/icu/src/main/native/DecimalFormatInterface.cpp
@@ -50,13 +50,13 @@
             default :
                 exception = env->FindClass("java/lang/RuntimeException");
         }
-        
+
         return (env->ThrowNew(exception, emsg) != 0);
     }
     return 0;
 }
 
-static jint openDecimalFormatImpl(JNIEnv *env, jclass clazz, jstring locale, 
+static jint openDecimalFormatImpl(JNIEnv *env, jclass clazz, jstring locale,
         jstring pattern) {
 
     // the errorcode returned by unum_open
@@ -70,9 +70,9 @@
     const char *localeChars = env->GetStringUTFChars(locale, NULL);
 
     // open a default type number format
-    UNumberFormat *fmt = unum_open(UNUM_PATTERN_DECIMAL, pattChars, pattLen, 
+    UNumberFormat *fmt = unum_open(UNUM_PATTERN_DECIMAL, pattChars, pattLen,
             localeChars, NULL, &status);
-    
+
     // release the allocated strings
     env->ReleaseStringChars(pattern, pattChars);
     env->ReleaseStringUTFChars(locale, localeChars);
@@ -88,20 +88,20 @@
 
 static void closeDecimalFormatImpl(JNIEnv *env, jclass clazz, jint addr) {
 
-    // get the pointer to the number format    
+    // get the pointer to the number format
     UNumberFormat *fmt = (UNumberFormat *)(int)addr;
 
     // close this number format
     unum_close(fmt);
 }
 
-static void setSymbol(JNIEnv *env, jclass clazz, jint addr, jint symbol, 
+static void setSymbol(JNIEnv *env, jclass clazz, jint addr, jint symbol,
         jstring text) {
-    
+
     // the errorcode returned by unum_setSymbol
     UErrorCode status = U_ZERO_ERROR;
 
-    // get the pointer to the number format    
+    // get the pointer to the number format
     UNumberFormat *fmt = (UNumberFormat *)(int)addr;
 
     // prepare the symbol string for the call to unum_setSymbol
@@ -109,9 +109,9 @@
     int textLen = env->GetStringLength(text);
 
     // set the symbol
-    unum_setSymbol(fmt, (UNumberFormatSymbol) symbol, textChars, textLen, 
+    unum_setSymbol(fmt, (UNumberFormatSymbol) symbol, textChars, textLen,
             &status);
-    
+
     // release previously allocated space
     env->ReleaseStringChars(text, textChars);
 
@@ -126,14 +126,14 @@
     // the errorcode returned by unum_getSymbol
     UErrorCode status = U_ZERO_ERROR;
 
-    // get the pointer to the number format    
+    // get the pointer to the number format
     UNumberFormat *fmt = (UNumberFormat *)(int)addr;
 
     UChar* result = NULL;
     resultlength=0;
 
     // find out how long the result will be
-    reslenneeded=unum_getSymbol(fmt, (UNumberFormatSymbol) symbol, result, 
+    reslenneeded=unum_getSymbol(fmt, (UNumberFormatSymbol) symbol, result,
             resultlength, &status);
 
     result = NULL;
@@ -141,7 +141,7 @@
         status=U_ZERO_ERROR;
         resultlength=reslenneeded+1;
         result=(UChar*)malloc(sizeof(UChar) * resultlength);
-        reslenneeded=unum_getSymbol(fmt, (UNumberFormatSymbol) symbol, result, 
+        reslenneeded=unum_getSymbol(fmt, (UNumberFormatSymbol) symbol, result,
                 resultlength, &status);
     }
     if (icuError(env, status) != FALSE) {
@@ -154,17 +154,17 @@
 
     return res;
 }
-    
-static void setAttribute(JNIEnv *env, jclass clazz, jint addr, jint symbol, 
+
+static void setAttribute(JNIEnv *env, jclass clazz, jint addr, jint symbol,
         jint value) {
-    
+
     UNumberFormat *fmt = (UNumberFormat *)(int)addr;
 
     unum_setAttribute(fmt, (UNumberFormatAttribute) symbol, value);
 }
-    
+
 static jint getAttribute(JNIEnv *env, jclass clazz, jint addr, jint symbol) {
-    
+
     UNumberFormat *fmt = (UNumberFormat *)(int)addr;
 
     int res = unum_getAttribute(fmt, (UNumberFormatAttribute) symbol);
@@ -172,27 +172,27 @@
     return res;
 }
 
-static void setTextAttribute(JNIEnv *env, jclass clazz, jint addr, jint symbol, 
+static void setTextAttribute(JNIEnv *env, jclass clazz, jint addr, jint symbol,
         jstring text) {
 
     // the errorcode returned by unum_setTextAttribute
     UErrorCode status = U_ZERO_ERROR;
 
-    // get the pointer to the number format    
+    // get the pointer to the number format
     UNumberFormat *fmt = (UNumberFormat *)(int)addr;
 
     const UChar *textChars = env->GetStringChars(text, NULL);
     int textLen = env->GetStringLength(text);
 
-    unum_setTextAttribute(fmt, (UNumberFormatTextAttribute) symbol, textChars, 
+    unum_setTextAttribute(fmt, (UNumberFormatTextAttribute) symbol, textChars,
             textLen, &status);
-    
+
     env->ReleaseStringChars(text, textChars);
 
     icuError(env, status);
 }
 
-static jstring getTextAttribute(JNIEnv *env, jclass clazz, jint addr, 
+static jstring getTextAttribute(JNIEnv *env, jclass clazz, jint addr,
         jint symbol) {
 
     uint32_t resultlength, reslenneeded;
@@ -200,14 +200,14 @@
     // the errorcode returned by unum_getTextAttribute
     UErrorCode status = U_ZERO_ERROR;
 
-    // get the pointer to the number format    
+    // get the pointer to the number format
     UNumberFormat *fmt = (UNumberFormat *)(int)addr;
 
     UChar* result = NULL;
     resultlength=0;
 
     // find out how long the result will be
-    reslenneeded=unum_getTextAttribute(fmt, (UNumberFormatTextAttribute) symbol, 
+    reslenneeded=unum_getTextAttribute(fmt, (UNumberFormatTextAttribute) symbol,
             result, resultlength, &status);
 
     result = NULL;
@@ -215,8 +215,8 @@
         status=U_ZERO_ERROR;
         resultlength=reslenneeded+1;
         result=(UChar*)malloc(sizeof(UChar) * resultlength);
-        reslenneeded=unum_getTextAttribute(fmt, 
-                (UNumberFormatTextAttribute) symbol, result, resultlength, 
+        reslenneeded=unum_getTextAttribute(fmt,
+                (UNumberFormatTextAttribute) symbol, result, resultlength,
                 &status);
     }
     if (icuError(env, status) != FALSE) {
@@ -230,13 +230,13 @@
     return res;
 }
 
-static void applyPatternImpl(JNIEnv *env, jclass clazz, jint addr, 
+static void applyPatternImpl(JNIEnv *env, jclass clazz, jint addr,
         jboolean localized, jstring pattern) {
 
     // the errorcode returned by unum_applyPattern
     UErrorCode status = U_ZERO_ERROR;
 
-    // get the pointer to the number format    
+    // get the pointer to the number format
     UNumberFormat *fmt = (UNumberFormat *)(int)addr;
 
     const UChar *pattChars = env->GetStringChars(pattern, NULL);
@@ -249,7 +249,7 @@
     icuError(env, status);
 }
 
-static jstring toPatternImpl(JNIEnv *env, jclass clazz, jint addr, 
+static jstring toPatternImpl(JNIEnv *env, jclass clazz, jint addr,
         jboolean localized) {
 
     uint32_t resultlength, reslenneeded;
@@ -257,7 +257,7 @@
     // the errorcode returned by unum_toPattern
     UErrorCode status = U_ZERO_ERROR;
 
-    // get the pointer to the number format    
+    // get the pointer to the number format
     UNumberFormat *fmt = (UNumberFormat *)(int)addr;
 
     UChar* result = NULL;
@@ -271,7 +271,7 @@
         status=U_ZERO_ERROR;
         resultlength=reslenneeded+1;
         result=(UChar*)malloc(sizeof(UChar) * resultlength);
-        reslenneeded=unum_toPattern(fmt, localized, result, resultlength, 
+        reslenneeded=unum_toPattern(fmt, localized, result, resultlength,
                 &status);
     }
     if (icuError(env, status) != FALSE) {
@@ -284,19 +284,19 @@
 
     return res;
 }
-    
-static jstring formatLong(JNIEnv *env, jclass clazz, jint addr, jlong value, 
+
+static jstring formatLong(JNIEnv *env, jclass clazz, jint addr, jlong value,
         jobject field, jstring fieldType, jobject attributes) {
 
     const char * fieldPositionClassName = "java/text/FieldPosition";
     const char * stringBufferClassName = "java/lang/StringBuffer";
     jclass fieldPositionClass = env->FindClass(fieldPositionClassName);
     jclass stringBufferClass = env->FindClass(stringBufferClassName);
-    jmethodID setBeginIndexMethodID = env->GetMethodID(fieldPositionClass, 
+    jmethodID setBeginIndexMethodID = env->GetMethodID(fieldPositionClass,
             "setBeginIndex", "(I)V");
-    jmethodID setEndIndexMethodID = env->GetMethodID(fieldPositionClass, 
+    jmethodID setEndIndexMethodID = env->GetMethodID(fieldPositionClass,
             "setEndIndex", "(I)V");
-    jmethodID appendMethodID = env->GetMethodID(stringBufferClass, 
+    jmethodID appendMethodID = env->GetMethodID(stringBufferClass,
             "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;");
 
     const char * fieldName = NULL;
@@ -331,7 +331,7 @@
     if(status==U_BUFFER_OVERFLOW_ERROR) {
         status=U_ZERO_ERROR;
 
-        result = (UChar*)malloc(sizeof(UChar) * (reslenneeded + 1));    
+        result = (UChar*)malloc(sizeof(UChar) * (reslenneeded + 1));
 
         res->extract(result, reslenneeded + 1, status);
     }
@@ -393,18 +393,18 @@
     return resulting;
 }
 
-static jstring formatDouble(JNIEnv *env, jclass clazz, jint addr, jdouble value, 
+static jstring formatDouble(JNIEnv *env, jclass clazz, jint addr, jdouble value,
         jobject field, jstring fieldType, jobject attributes) {
 
     const char * fieldPositionClassName = "java/text/FieldPosition";
     const char * stringBufferClassName = "java/lang/StringBuffer";
     jclass fieldPositionClass = env->FindClass(fieldPositionClassName);
     jclass stringBufferClass = env->FindClass(stringBufferClassName);
-    jmethodID setBeginIndexMethodID = env->GetMethodID(fieldPositionClass, 
+    jmethodID setBeginIndexMethodID = env->GetMethodID(fieldPositionClass,
             "setBeginIndex", "(I)V");
-    jmethodID setEndIndexMethodID = env->GetMethodID(fieldPositionClass, 
+    jmethodID setEndIndexMethodID = env->GetMethodID(fieldPositionClass,
             "setEndIndex", "(I)V");
-    jmethodID appendMethodID = env->GetMethodID(stringBufferClass, 
+    jmethodID appendMethodID = env->GetMethodID(stringBufferClass,
             "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;");
 
     const char * fieldName = NULL;
@@ -439,7 +439,7 @@
     if(status==U_BUFFER_OVERFLOW_ERROR) {
         status=U_ZERO_ERROR;
 
-        result = (UChar*)malloc(sizeof(UChar) * (reslenneeded + 1));    
+        result = (UChar*)malloc(sizeof(UChar) * (reslenneeded + 1));
 
         res->extract(result, reslenneeded + 1, status);
 
@@ -502,7 +502,7 @@
     return resulting;
 }
 
-static jstring formatDigitList(JNIEnv *env, jclass clazz, jint addr, jstring value, 
+static jstring formatDigitList(JNIEnv *env, jclass clazz, jint addr, jstring value,
         jobject field, jstring fieldType, jobject attributes, jint scale) {
 
     // const char * valueUTF = env->GetStringUTFChars(value, NULL);
@@ -521,21 +521,36 @@
 
     uint32_t reslenneeded;
 
-    bool isInteger = (scale == 0);
-
     // prepare digit list
 
-    const char *digits = env->GetStringUTFChars(value, NULL);
+    const char *valueChars = env->GetStringUTFChars(value, NULL);
 
-    // length must be string lengt + 2 because there's an additional
-    // character in front of the string ("+" or "-") and a \0 at the end
-    DigitList digitList(strlen(digits) + 2);
-    digitList.fCount = strlen(digits);
+    bool isInteger = (scale == 0);
+    bool isPositive = (*valueChars != '-');
+
+    // skip the '-' if the number is negative
+    const char *digits = (isPositive ? valueChars : valueChars + 1);
+    int length = strlen(digits);
+
+    // The length of our digit list buffer must be the actual string length + 3,
+    // because ICU will append some additional characters at the head and at the
+    // tail of the string, in order to keep strtod() happy:
+    //
+    // - The sign "+" or "-" is appended at the head
+    // - The exponent "e" and the "\0" terminator is appended at the tail
+    //
+    // In retrospect, the changes to ICU's DigitList that were necessary for
+    // big numbers look a bit hacky. It would make sense to rework all this
+    // once ICU 4.x has been integrated into Android. Ideally, big number
+    // support would make it into ICU itself, so we don't need our private
+    // fix anymore.
+    DigitList digitList(length + 3);
+    digitList.fCount = length;
     strcpy(digitList.fDigits, digits);
-    env->ReleaseStringUTFChars(value, digits);
+    env->ReleaseStringUTFChars(value, valueChars);
 
     digitList.fDecimalAt = digitList.fCount - scale;
-    digitList.fIsPositive = (*digits != '-');
+    digitList.fIsPositive = isPositive;
     digitList.fRoundingMode = DecimalFormat::kRoundHalfUp;
 
     UChar *result = NULL;
@@ -548,10 +563,9 @@
     UErrorCode status = U_ZERO_ERROR;
 
     DecimalFormat::AttributeBuffer *attrBuffer = NULL;
-    attrBuffer = (DecimalFormat::AttributeBuffer *) malloc(sizeof(DecimalFormat::AttributeBuffer));
+    attrBuffer = (DecimalFormat::AttributeBuffer *) calloc(sizeof(DecimalFormat::AttributeBuffer), 1);
     attrBuffer->bufferSize = 128;
-    attrBuffer->buffer = (char *) malloc(129 * sizeof(char));
-    attrBuffer->buffer[0] = '\0';
+    attrBuffer->buffer = (char *) calloc(129 * sizeof(char), 1);
 
     DecimalFormat *fmt = (DecimalFormat *)(int)addr;
 
@@ -564,7 +578,7 @@
     if(status==U_BUFFER_OVERFLOW_ERROR) {
         status=U_ZERO_ERROR;
 
-        result = (UChar*)malloc(sizeof(UChar) * (reslenneeded + 1));    
+        result = (UChar*)malloc(sizeof(UChar) * (reslenneeded + 1));
 
         res.extract(result, reslenneeded + 1, status);
 
@@ -584,7 +598,7 @@
         }
         free(attrBuffer->buffer);
         free(attrBuffer);
-        return NULL;        
+        return NULL;
     }
 
     int attrLength = (strlen(attrBuffer->buffer) + 1 );
@@ -596,7 +610,7 @@
             // prepare the classes and method ids
             const char * stringBufferClassName = "java/lang/StringBuffer";
             jclass stringBufferClass = env->FindClass(stringBufferClassName);
-            jmethodID appendMethodID = env->GetMethodID(stringBufferClass, 
+            jmethodID appendMethodID = env->GetMethodID(stringBufferClass,
                     "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;");
 
             jstring attrString = env->NewStringUTF(attrBuffer->buffer + 1);  // cut off the leading ';'
@@ -655,7 +669,7 @@
     return resulting;
 }
 
-static jobject parse(JNIEnv *env, jclass clazz, jint addr, jstring text, 
+static jobject parse(JNIEnv *env, jclass clazz, jint addr, jstring text,
         jobject position) {
 
     const char * textUTF = env->GetStringUTFChars(text, NULL);
@@ -680,11 +694,11 @@
     jclass bigDecimalClass = env->FindClass(bigDecimalClassName);
     jclass bigIntegerClass = env->FindClass(bigIntegerClassName);
 
-    jmethodID getIndexMethodID = env->GetMethodID(parsePositionClass, 
+    jmethodID getIndexMethodID = env->GetMethodID(parsePositionClass,
             "getIndex", "()I");
-    jmethodID setIndexMethodID = env->GetMethodID(parsePositionClass, 
+    jmethodID setIndexMethodID = env->GetMethodID(parsePositionClass,
             "setIndex", "(I)V");
-    jmethodID setErrorIndexMethodID = env->GetMethodID(parsePositionClass, 
+    jmethodID setErrorIndexMethodID = env->GetMethodID(parsePositionClass,
             "setErrorIndex", "(I)V");
 
     jmethodID longInitMethodID = env->GetMethodID(longClass, "<init>", "(J)V");
@@ -696,8 +710,8 @@
     bool resultAssigned;
     int parsePos = env->CallIntMethod(position, getIndexMethodID, NULL);
 
-    // make sure the ParsePosition is valid. Actually icu4c would parse a number 
-    // correctly even if the parsePosition is set to -1, but since the RI fails 
+    // make sure the ParsePosition is valid. Actually icu4c would parse a number
+    // correctly even if the parsePosition is set to -1, but since the RI fails
     // for that case we have to fail too
     if(parsePos < 0 || parsePos > strlength) {
         return NULL;
@@ -707,9 +721,9 @@
 
     const UnicodeString src((UChar*)str, strlength, strlength);
     ParsePosition pp;
-    
+
     pp.setIndex(parsePos);
-    
+
     DigitList digits;
 
     ((const DecimalFormat*)fmt)->parse(src, resultAssigned, res, pp, FALSE, digits);
@@ -719,8 +733,8 @@
     if(pp.getErrorIndex() == -1) {
         parsePos = pp.getIndex();
     } else {
-        env->CallVoidMethod(position, setErrorIndexMethodID, 
-                (jint) pp.getErrorIndex());        
+        env->CallVoidMethod(position, setErrorIndexMethodID,
+                (jint) pp.getErrorIndex());
         return NULL;
     }
 
@@ -745,17 +759,17 @@
         case Formattable::kDouble:
             resultDouble = res.getDouble();
             env->CallVoidMethod(position, setIndexMethodID, (jint) parsePos);
-            return env->NewObject(doubleClass, dblInitMethodID, 
+            return env->NewObject(doubleClass, dblInitMethodID,
                     (jdouble) resultDouble);
         case Formattable::kLong:
             resultLong = res.getLong();
             env->CallVoidMethod(position, setIndexMethodID, (jint) parsePos);
-            return env->NewObject(longClass, longInitMethodID, 
+            return env->NewObject(longClass, longInitMethodID,
                     (jlong) resultLong);
         case Formattable::kInt64:
             resultInt64 = res.getInt64();
             env->CallVoidMethod(position, setIndexMethodID, (jint) parsePos);
-            return env->NewObject(longClass, longInitMethodID, 
+            return env->NewObject(longClass, longInitMethodID,
                     (jlong) resultInt64);
         default:
             return NULL;
@@ -805,7 +819,7 @@
 
 static JNINativeMethod gMethods[] = {
     /* name, signature, funcPtr */
-    {"openDecimalFormatImpl", "(Ljava/lang/String;Ljava/lang/String;)I", 
+    {"openDecimalFormatImpl", "(Ljava/lang/String;Ljava/lang/String;)I",
             (void*) openDecimalFormatImpl},
     {"closeDecimalFormatImpl", "(I)V", (void*) closeDecimalFormatImpl},
     {"setSymbol", "(IILjava/lang/String;)V", (void*) setSymbol},
@@ -816,22 +830,22 @@
     {"getTextAttribute", "(II)Ljava/lang/String;", (void*) getTextAttribute},
     {"applyPatternImpl", "(IZLjava/lang/String;)V", (void*) applyPatternImpl},
     {"toPatternImpl", "(IZ)Ljava/lang/String;", (void*) toPatternImpl},
-    {"format", 
-            "(IJLjava/text/FieldPosition;Ljava/lang/String;Ljava/lang/StringBuffer;)Ljava/lang/String;", 
+    {"format",
+            "(IJLjava/text/FieldPosition;Ljava/lang/String;Ljava/lang/StringBuffer;)Ljava/lang/String;",
             (void*) formatLong},
-    {"format", 
-            "(IDLjava/text/FieldPosition;Ljava/lang/String;Ljava/lang/StringBuffer;)Ljava/lang/String;", 
+    {"format",
+            "(IDLjava/text/FieldPosition;Ljava/lang/String;Ljava/lang/StringBuffer;)Ljava/lang/String;",
             (void*) formatDouble},
-    {"format", 
-            "(ILjava/lang/String;Ljava/text/FieldPosition;Ljava/lang/String;Ljava/lang/StringBuffer;I)Ljava/lang/String;", 
+    {"format",
+            "(ILjava/lang/String;Ljava/text/FieldPosition;Ljava/lang/String;Ljava/lang/StringBuffer;I)Ljava/lang/String;",
             (void*) formatDigitList},
-    {"parse", 
-            "(ILjava/lang/String;Ljava/text/ParsePosition;)Ljava/lang/Number;", 
+    {"parse",
+            "(ILjava/lang/String;Ljava/text/ParsePosition;)Ljava/lang/Number;",
             (void*) parse},
     {"cloneImpl", "(I)I", (void*) cloneImpl}
 };
 int register_com_ibm_icu4jni_text_NativeDecimalFormat(JNIEnv* env) {
-    return jniRegisterNativeMethods(env, 
-            "com/ibm/icu4jni/text/NativeDecimalFormat", gMethods, 
+    return jniRegisterNativeMethods(env,
+            "com/ibm/icu4jni/text/NativeDecimalFormat", gMethods,
             NELEM(gMethods));
 }
diff --git a/luni/src/main/java/java/io/BufferedInputStream.java b/luni/src/main/java/java/io/BufferedInputStream.java
index 1720366..0b9afc3 100644
--- a/luni/src/main/java/java/io/BufferedInputStream.java
+++ b/luni/src/main/java/java/io/BufferedInputStream.java
@@ -108,7 +108,7 @@
         Logger.global.info(
                 "Default buffer size used in BufferedInputStream " +
                 "constructor. It would be " +
-                "better to be explicit if a 8k buffer is required.");
+                "better to be explicit if an 8k buffer is required.");
         // END android-added
     }
 
diff --git a/luni/src/main/java/java/io/BufferedOutputStream.java b/luni/src/main/java/java/io/BufferedOutputStream.java
index 55419d1..835d13f 100644
--- a/luni/src/main/java/java/io/BufferedOutputStream.java
+++ b/luni/src/main/java/java/io/BufferedOutputStream.java
@@ -80,7 +80,7 @@
         Logger.global.info(
                 "Default buffer size used in BufferedOutputStream " +
                 "constructor. It would be " +
-                "better to be explicit if a 8k buffer is required.");
+                "better to be explicit if an 8k buffer is required.");
         // END android-added
     }
 
diff --git a/luni/src/main/java/java/io/BufferedReader.java b/luni/src/main/java/java/io/BufferedReader.java
index c490b89..e82e538 100644
--- a/luni/src/main/java/java/io/BufferedReader.java
+++ b/luni/src/main/java/java/io/BufferedReader.java
@@ -80,7 +80,7 @@
         Logger.global.info(
                 "Default buffer size used in BufferedReader " +
                 "constructor. It would be " +
-                "better to be explicit if a 8k-char buffer is required.");
+                "better to be explicit if an 8k-char buffer is required.");
         // END android-added
     }
 
diff --git a/luni/src/main/java/java/io/BufferedWriter.java b/luni/src/main/java/java/io/BufferedWriter.java
index 66bfcc9..761b9c5 100644
--- a/luni/src/main/java/java/io/BufferedWriter.java
+++ b/luni/src/main/java/java/io/BufferedWriter.java
@@ -80,7 +80,7 @@
         Logger.global.info(
                 "Default buffer size used in BufferedWriter " +
                 "constructor. It would be " +
-                "better to be explicit if a 8k-char buffer is required.");
+                "better to be explicit if an 8k-char buffer is required.");
         // END android-added
     }
 
diff --git a/luni/src/main/java/java/util/ArrayList.java b/luni/src/main/java/java/util/ArrayList.java
index 5ea316a..3bae372 100644
--- a/luni/src/main/java/java/util/ArrayList.java
+++ b/luni/src/main/java/java/util/ArrayList.java
@@ -112,7 +112,7 @@
      * @param object
      *            the object to add.
      * @throws IndexOutOfBoundsException
-     *             when {@code location < 0 || >= size()}
+     *             when {@code location < 0 || > size()}
      * @since Android 1.0
      */
     @Override
diff --git a/luni/src/test/java/org/apache/harmony/luni/tests/java/lang/StringTest.java b/luni/src/test/java/org/apache/harmony/luni/tests/java/lang/StringTest.java
index 6c995bb..30510c2 100644
--- a/luni/src/test/java/org/apache/harmony/luni/tests/java/lang/StringTest.java
+++ b/luni/src/test/java/org/apache/harmony/luni/tests/java/lang/StringTest.java
@@ -17,15 +17,14 @@
 
 package org.apache.harmony.luni.tests.java.lang;
 
-import dalvik.annotation.TestTargets;
 import dalvik.annotation.TestLevel;
-import dalvik.annotation.TestTargetNew;
 import dalvik.annotation.TestTargetClass;
+import dalvik.annotation.TestTargetNew;
 
 import junit.framework.TestCase;
 
 import java.io.UnsupportedEncodingException;
-import java.lang.reflect.Constructor;
+import java.math.BigDecimal;
 
 @TestTargetClass(String.class) 
 public class StringTest extends TestCase {
@@ -701,4 +700,40 @@
         } catch (IndexOutOfBoundsException e) {
         }
     }
+
+    @TestTargetNew(
+        level = TestLevel.ADDITIONAL,
+        notes = "Regression test for some existing bugs and crashes",
+        method = "format",
+        args = { String.class, Object.class }
+    )
+    public void testProblemCases() {
+        BigDecimal[] input = new BigDecimal[] {
+            new BigDecimal("20.00000"),
+            new BigDecimal("20.000000"),
+            new BigDecimal(".2"),
+            new BigDecimal("2"),
+            new BigDecimal("-2"),
+            new BigDecimal("200000000000000000000000"),
+            new BigDecimal("20000000000000000000000000000000000000000000000000")
+        };
+
+        String[] output = new String[] {
+                "20.00",
+                "20.00",
+                "0.20",
+                "2.00",
+                "-2.00",
+                "200000000000000000000000.00",
+                "20000000000000000000000000000000000000000000000000.00"
+        };
+        
+        for (int i = 0; i < input.length; i++) {
+            String result = String.format("%.2f", input[i]);
+            assertEquals("Format test for \"" + input[i] + "\" failed, " +
+                    "expected=" + output[i] + ", " +
+                    "actual=" + result, output[i], result);
+        }
+    }
+    
 }
diff --git a/run-core-tests b/run-core-tests
index 3656e2d..25e53ee 100755
--- a/run-core-tests
+++ b/run-core-tests
@@ -26,6 +26,5 @@
 chmod 777 $tmp
   
 exec dalvikvm -Duser.language=en -Duser.region=US -Djava.io.tmpdir=$tmp \
-     -Xbootclasspath:$BOOTCLASSPATH \
-     -classpath /system/framework/core-tests.jar \
+     -Xbootclasspath:$BOOTCLASSPATH:/system/framework/core-tests.jar \
      com.google.coretests.Main "$@"
diff --git a/security/src/main/java/org/bouncycastle/asn1/x509/X509NameTokenizer.java b/security/src/main/java/org/bouncycastle/asn1/x509/X509NameTokenizer.java
index 035e924..8f0d08b 100644
--- a/security/src/main/java/org/bouncycastle/asn1/x509/X509NameTokenizer.java
+++ b/security/src/main/java/org/bouncycastle/asn1/x509/X509NameTokenizer.java
@@ -66,6 +66,17 @@
             {
                 if (escaped || quoted)
                 {
+                    // BEGIN android-added
+                    // copied from a newer version of BouncyCastle
+                    if (c == '#' && buf.charAt(buf.length() - 1) == '=')
+                    {
+                        buf.append('\\');
+                    }
+                    else if (c == '+' && seperator != '+')
+                    {
+                        buf.append('\\');
+                    }
+                    // END android-added
                     buf.append(c);
                     escaped = false;
                 }
@@ -88,4 +99,4 @@
         index = end;
         return buf.toString().trim();
     }
-}
+}
\ No newline at end of file
diff --git a/x-net/src/main/java/javax/net/ssl/DefaultHostnameVerifier.java b/x-net/src/main/java/javax/net/ssl/DefaultHostnameVerifier.java
index 6469c93..6adca42 100644
--- a/x-net/src/main/java/javax/net/ssl/DefaultHostnameVerifier.java
+++ b/x-net/src/main/java/javax/net/ssl/DefaultHostnameVerifier.java
@@ -15,22 +15,352 @@
  *  limitations under the License.
  */
 
+// BEGIN android-added
+// Copied and condensed code taken from the Apache HttpClient. Also slightly
+// modified, so it matches the package/class structure of the core libraries.
+// This HostnameVerifier does checking similar to what the RI and popular
+// browsers do.
+// END android-added
+
 package javax.net.ssl;
 
+import org.apache.harmony.luni.util.Inet6Util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.StringTokenizer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+
 /**
- * Default implementation of javax.net.ssl.HostnameVerifier
+ * A HostnameVerifier that works the same way as Curl and Firefox.
+ * <p/>
+ * The hostname must match either the first CN, or any of the subject-alts.
+ * A wildcard can occur in the CN, and in any of the subject-alts.
+ * <p/>
+ * The only difference between BROWSER_COMPATIBLE and STRICT is that a wildcard 
+ * (such as "*.foo.com") with BROWSER_COMPATIBLE matches all subdomains, 
+ * including "a.b.foo.com".
  * 
- * @since Android 1.0
+ * @author Julius Davies
  */
 class DefaultHostnameVerifier implements HostnameVerifier {
 
     /**
-     * DefaultHostnameVerifier assumes the connection should not be permitted.
-     * 
-     * @param hostname
-     * @param session
+     * This contains a list of 2nd-level domains that aren't allowed to
+     * have wildcards when combined with country-codes.
+     * For example: [*.co.uk].
+     * <p/>
+     * The [*.co.uk] problem is an interesting one.  Should we just hope
+     * that CA's would never foolishly allow such a certificate to happen?
+     * Looks like we're the only implementation guarding against this.
+     * Firefox, Curl, Sun Java 1.4, 5, 6 don't bother with this check.
      */
-    public boolean verify(String hostname, SSLSession session) {
-        return false;
+    private final static String[] BAD_COUNTRY_2LDS =
+          { "ac", "co", "com", "ed", "edu", "go", "gouv", "gov", "info",
+            "lg", "ne", "net", "or", "org" };
+
+    static {
+        // Just in case developer forgot to manually sort the array.  :-)
+        Arrays.sort(BAD_COUNTRY_2LDS);
     }
+
+    public DefaultHostnameVerifier() {
+        super();
+    }
+
+    public final void verify(String host, SSLSocket ssl)
+          throws IOException {
+        if(host == null) {
+            throw new NullPointerException("host to verify is null");
+        }
+
+        ssl.startHandshake();
+        SSLSession session = ssl.getSession();
+        if(session == null) {
+            // In our experience this only happens under IBM 1.4.x when
+            // spurious (unrelated) certificates show up in the server'
+            // chain.  Hopefully this will unearth the real problem:
+            InputStream in = ssl.getInputStream();
+            in.available();
+            /*
+              If you're looking at the 2 lines of code above because
+              you're running into a problem, you probably have two
+              options:
+
+                #1.  Clean up the certificate chain that your server
+                     is presenting (e.g. edit "/etc/apache2/server.crt"
+                     or wherever it is your server's certificate chain
+                     is defined).
+
+                                           OR
+
+                #2.   Upgrade to an IBM 1.5.x or greater JVM, or switch
+                      to a non-IBM JVM.
+            */
+
+            // If ssl.getInputStream().available() didn't cause an
+            // exception, maybe at least now the session is available?
+            session = ssl.getSession();
+            if(session == null) {
+                // If it's still null, probably a startHandshake() will
+                // unearth the real problem.
+                ssl.startHandshake();
+
+                // Okay, if we still haven't managed to cause an exception,
+                // might as well go for the NPE.  Or maybe we're okay now?
+                session = ssl.getSession();
+            }
+        }
+
+        Certificate[] certs = session.getPeerCertificates();
+        X509Certificate x509 = (X509Certificate) certs[0];
+        verify(host, x509);
+    }
+
+    public final boolean verify(String host, SSLSession session) {
+        try {
+            Certificate[] certs = session.getPeerCertificates();
+            X509Certificate x509 = (X509Certificate) certs[0];
+            verify(host, x509);
+            return true;
+        }
+        catch(SSLException e) {
+            return false;
+        }
+    }
+
+    public final void verify(String host, X509Certificate cert)
+          throws SSLException {
+        String[] cns = getCNs(cert);
+        String[] subjectAlts = getDNSSubjectAlts(cert);
+        verify(host, cns, subjectAlts);
+    }
+
+    public final void verify(final String host, final String[] cns,
+                             final String[] subjectAlts,
+                             final boolean strictWithSubDomains)
+          throws SSLException {
+
+        // Build the list of names we're going to check.  Our DEFAULT and
+        // STRICT implementations of the HostnameVerifier only use the
+        // first CN provided.  All other CNs are ignored.
+        // (Firefox, wget, curl, Sun Java 1.4, 5, 6 all work this way).
+        LinkedList<String> names = new LinkedList<String>();
+        if(cns != null && cns.length > 0 && cns[0] != null) {
+            names.add(cns[0]);
+        }
+        if(subjectAlts != null) {
+            for (String subjectAlt : subjectAlts) {
+                if (subjectAlt != null) {
+                    names.add(subjectAlt);
+                }
+            }
+        }
+
+        if(names.isEmpty()) {
+            String msg = "Certificate for <" + host +
+                         "> doesn't contain CN or DNS subjectAlt";
+            throw new SSLException(msg);
+        }
+
+        // StringBuffer for building the error message.
+        StringBuffer buf = new StringBuffer();
+
+        // We're can be case-insensitive when comparing the host we used to
+        // establish the socket to the hostname in the certificate.
+        String hostName = host.trim().toLowerCase(Locale.ENGLISH);
+        boolean match = false;
+        for(Iterator<String> it = names.iterator(); it.hasNext();) {
+            // Don't trim the CN, though!
+            String cn = it.next();
+            cn = cn.toLowerCase(Locale.ENGLISH);
+            // Store CN in StringBuffer in case we need to report an error.
+            buf.append(" <");
+            buf.append(cn);
+            buf.append('>');
+            if(it.hasNext()) {
+                buf.append(" OR");
+            }
+
+            // The CN better have at least two dots if it wants wildcard
+            // action.  It also can't be [*.co.uk] or [*.co.jp] or
+            // [*.org.uk], etc...
+            boolean doWildcard = cn.startsWith("*.") &&
+                                 cn.lastIndexOf('.') >= 0 &&
+                                 acceptableCountryWildcard(cn) &&
+                                 !Inet6Util.isValidIPV4Address(host);
+
+            if(doWildcard) {
+                match = hostName.endsWith(cn.substring(1));
+                if(match && strictWithSubDomains) {
+                    // If we're in strict mode, then [*.foo.com] is not
+                    // allowed to match [a.b.foo.com]
+                    match = countDots(hostName) == countDots(cn);
+                }
+            } else {
+                match = hostName.equals(cn);
+            }
+            if(match) {
+                break;
+            }
+        }
+        if(!match) {
+            throw new SSLException("hostname in certificate didn't match: <" + 
+                                   host + "> !=" + buf);
+        }
+    }
+
+    public static boolean acceptableCountryWildcard(String cn) {
+        int cnLen = cn.length();
+        if(cnLen >= 7 && cnLen <= 9) {
+            // Look for the '.' in the 3rd-last position:
+            if(cn.charAt(cnLen - 3) == '.') {
+                // Trim off the [*.] and the [.XX].
+                String s = cn.substring(2, cnLen - 3);
+                // And test against the sorted array of bad 2lds:
+                int x = Arrays.binarySearch(BAD_COUNTRY_2LDS, s);
+                return x < 0;
+            }
+        }
+        return true;
+    }
+
+    public static String[] getCNs(X509Certificate cert) {
+        LinkedList<String> cnList = new LinkedList<String>();
+        /*
+          Sebastian Hauer's original StrictSSLProtocolSocketFactory used
+          getName() and had the following comment:
+
+              Parses a X.500 distinguished name for the value of the
+              "Common Name" field.  This is done a bit sloppy right
+              now and should probably be done a bit more according to
+              <code>RFC 2253</code>.
+
+          I've noticed that toString() seems to do a better job than
+          getName() on these X500Principal objects, so I'm hoping that
+          addresses Sebastian's concern.
+
+          For example, getName() gives me this:
+          1.2.840.113549.1.9.1=#16166a756c6975736461766965734063756362632e636f6d
+
+          whereas toString() gives me this:
+          EMAILADDRESS=juliusdavies@cucbc.com
+
+          Looks like toString() even works with non-ascii domain names!
+          I tested it with "&#x82b1;&#x5b50;.co.jp" and it worked fine.
+        */
+        String subjectPrincipal = cert.getSubjectX500Principal().toString();
+        StringTokenizer st = new StringTokenizer(subjectPrincipal, ",");
+        while(st.hasMoreTokens()) {
+            String tok = st.nextToken();
+            int x = tok.indexOf("CN=");
+            if(x >= 0) {
+                cnList.add(tok.substring(x + 3));
+            }
+        }
+        if(!cnList.isEmpty()) {
+            String[] cns = new String[cnList.size()];
+            cnList.toArray(cns);
+            return cns;
+        } else {
+            return null;
+        }
+    }
+
+
+    /**
+     * Extracts the array of SubjectAlt DNS names from an X509Certificate.
+     * Returns null if there aren't any.
+     * <p/>
+     * Note:  Java doesn't appear able to extract international characters
+     * from the SubjectAlts.  It can only extract international characters
+     * from the CN field.
+     * <p/>
+     * (Or maybe the version of OpenSSL I'm using to test isn't storing the
+     * international characters correctly in the SubjectAlts?).
+     *
+     * @param cert X509Certificate
+     * @return Array of SubjectALT DNS names stored in the certificate.
+     */
+    public static String[] getDNSSubjectAlts(X509Certificate cert) {
+        LinkedList<String> subjectAltList = new LinkedList<String>();
+        Collection<List<?>> c = null;
+        try {
+            c = cert.getSubjectAlternativeNames();
+        }
+        catch(CertificateParsingException cpe) {
+            Logger.getLogger(DefaultHostnameVerifier.class.getName())
+                    .log(Level.FINE, "Error parsing certificate.", cpe);
+        }
+        if(c != null) {
+            for (List<?> aC : c) {
+                List<?> list = aC;
+                int type = ((Integer) list.get(0)).intValue();
+                // If type is 2, then we've got a dNSName
+                if (type == 2) {
+                    String s = (String) list.get(1);
+                    subjectAltList.add(s);
+                }
+            }
+        }
+        if(!subjectAltList.isEmpty()) {
+            String[] subjectAlts = new String[subjectAltList.size()];
+            subjectAltList.toArray(subjectAlts);
+            return subjectAlts;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Counts the number of dots "." in a string.
+     * @param s  string to count dots from
+     * @return  number of dots
+     */
+    public static int countDots(final String s) {
+        int count = 0;
+        for(int i = 0; i < s.length(); i++) {
+            if(s.charAt(i) == '.') {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    /**
+     * Checks to see if the supplied hostname matches any of the supplied CNs
+     * or "DNS" Subject-Alts.  Most implementations only look at the first CN,
+     * and ignore any additional CNs.  Most implementations do look at all of
+     * the "DNS" Subject-Alts. The CNs or Subject-Alts may contain wildcards
+     * according to RFC 2818.
+     *
+     * @param cns         CN fields, in order, as extracted from the X.509
+     *                    certificate.
+     * @param subjectAlts Subject-Alt fields of type 2 ("DNS"), as extracted
+     *                    from the X.509 certificate.
+     * @param host        The hostname to verify.
+     * @throws SSLException If verification failed.
+     */
+    public final void verify(
+            final String host, 
+            final String[] cns,
+            final String[] subjectAlts) throws SSLException {
+        verify(host, cns, subjectAlts, false);
+    }
+    
 }
diff --git a/x-net/src/main/java/javax/net/ssl/SSLSocketFactory.java b/x-net/src/main/java/javax/net/ssl/SSLSocketFactory.java
index cb821c2..2b7c03e 100644
--- a/x-net/src/main/java/javax/net/ssl/SSLSocketFactory.java
+++ b/x-net/src/main/java/javax/net/ssl/SSLSocketFactory.java
@@ -24,6 +24,7 @@
 // BEGIN android-added
 import java.lang.reflect.Method;
 import java.net.UnknownHostException;
+import java.util.logging.Logger;
 // END android-added
 
 import javax.net.SocketFactory;
@@ -57,60 +58,56 @@
      * @since Android 1.0
      */
     public static SocketFactory getDefault() {
-        if (defaultSocketFactory != null) {
+        synchronized (SSLSocketFactory.class) {
+            if (defaultSocketFactory != null) {
+                // BEGIN android-added
+                log("SSLSocketFactory", "Using factory " + defaultSocketFactory);
+                // END android-added
+                return defaultSocketFactory;
+            }
+            if (defaultName == null) {
+                AccessController.doPrivileged(new java.security.PrivilegedAction(){
+                    public Object run() {
+                        defaultName = Security.getProperty("ssl.SocketFactory.provider");
+                        if (defaultName != null) {
+                            ClassLoader cl = Thread.currentThread().getContextClassLoader();
+                            if (cl == null) {
+                                cl = ClassLoader.getSystemClassLoader();
+                            }
+                            try {
+                                defaultSocketFactory = (SocketFactory) Class.forName(
+                                        defaultName, true, cl).newInstance();
+                             } catch (Exception e) {
+                                return e;
+                            }
+                        }
+                        return null;
+                    }
+                });
+            }
+
+            if (defaultSocketFactory == null) {
+                // Try to find in providers
+                SSLContext context = DefaultSSLContext.getContext();
+                if (context != null) {
+                    defaultSocketFactory = context.getSocketFactory();
+                }
+            }
+            if (defaultSocketFactory == null) {
+                // Use internal implementation
+                defaultSocketFactory = new DefaultSSLSocketFactory("No SSLSocketFactory installed");
+            }
             // BEGIN android-added
             log("SSLSocketFactory", "Using factory " + defaultSocketFactory);
             // END android-added
             return defaultSocketFactory;
         }
-        if (defaultName == null) {
-            AccessController.doPrivileged(new java.security.PrivilegedAction(){
-                public Object run() {
-                    defaultName = Security.getProperty("ssl.SocketFactory.provider");
-                    if (defaultName != null) {    
-                        ClassLoader cl = Thread.currentThread().getContextClassLoader();
-                        if (cl == null) {
-                            cl = ClassLoader.getSystemClassLoader();
-                        }
-                        try {
-                            defaultSocketFactory = (SocketFactory) Class.forName(
-                                    defaultName, true, cl).newInstance();
-                         } catch (Exception e) {
-                            return e;
-                        }
-                    }
-                    return null;
-                }
-            });
-        }
-
-        if (defaultSocketFactory == null) {
-            // Try to find in providers
-            SSLContext context = DefaultSSLContext.getContext();
-            if (context != null) {
-                defaultSocketFactory = context.getSocketFactory();
-            }
-        }
-        if (defaultSocketFactory == null) {
-            // Use internal implementation
-            defaultSocketFactory = new DefaultSSLSocketFactory("No SSLSocketFactory installed");
-        }
-        // BEGIN android-added
-        log("SSLSocketFactory", "Using factory " + defaultSocketFactory);
-        // END android-added
-        return defaultSocketFactory;
     }
 
     // BEGIN android-added
     @SuppressWarnings("unchecked")
     private static void log(String tag, String msg) {
-        try {
-            Class clazz = Class.forName("android.util.Log");
-            Method method = clazz.getMethod("d", new Class[] { String.class, String.class });
-            method.invoke(null, new Object[] { tag, msg });
-        } catch (Exception ex) {
-            // Silently ignore.
-        }
+        Logger.getLogger(tag).info(msg);
     }
     // END android-added
 
diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/AbstractSessionContext.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/AbstractSessionContext.java
new file mode 100644
index 0000000..a95d38f
--- /dev/null
+++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/AbstractSessionContext.java
@@ -0,0 +1,212 @@
+/*
+ * 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 org.apache.harmony.xnet.provider.jsse;
+
+import java.util.*;
+import java.util.logging.Level;
+import java.io.*;
+
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSessionContext;
+import javax.security.cert.X509Certificate;
+import javax.security.cert.CertificateEncodingException;
+import javax.security.cert.CertificateException;
+
+/**
+ * Supports SSL session caches.
+ */
+abstract class AbstractSessionContext implements SSLSessionContext {
+
+    volatile int maximumSize;
+    volatile int timeout;
+
+    final SSLParameters parameters;
+
+    /** Identifies OpenSSL sessions. */
+    static final int OPEN_SSL = 1;
+
+    /**
+     * Constructs a new session context.
+     *
+     * @param parameters
+     * @param maximumSize of cache
+     * @param timeout for cache entries
+     */
+    AbstractSessionContext(SSLParameters parameters, int maximumSize,
+            int timeout) {
+        this.parameters = parameters;
+        this.maximumSize = maximumSize;
+        this.timeout = timeout;
+    }
+
+    /**
+     * Returns the collection of sessions ordered by least-recently-used first.
+     */
+    abstract Iterator<SSLSession> sessionIterator();
+
+    public final Enumeration getIds() {
+        final Iterator<SSLSession> iterator = sessionIterator();
+        return new Enumeration<byte[]>() {
+            public boolean hasMoreElements() {
+                return iterator.hasNext();
+            }
+            public byte[] nextElement() {
+                return iterator.next().getId();
+            }
+        };
+    }
+
+    public final int getSessionCacheSize() {
+        return maximumSize;
+    }
+
+    public final int getSessionTimeout() {
+        return timeout;
+    }
+
+    /**
+     * Makes sure cache size is < maximumSize.
+     */
+    abstract void trimToSize();
+
+    public final void setSessionCacheSize(int size)
+            throws IllegalArgumentException {
+        if (size < 0) {
+            throw new IllegalArgumentException("size < 0");
+        }
+
+        int oldMaximum = maximumSize;
+        maximumSize = size;
+
+        // Trim cache to size if necessary.
+        if (size < oldMaximum) {
+            trimToSize();
+        }
+    }
+
+    /**
+     * Converts the given session to bytes.
+     *
+     * @return session data as bytes or null if the session can't be converted
+     */
+    byte[] toBytes(SSLSession session) {
+        // TODO: Support SSLSessionImpl, too.
+        if (!(session instanceof OpenSSLSessionImpl)) {
+            return null;
+        }
+
+        OpenSSLSessionImpl sslSession = (OpenSSLSessionImpl) session;
+        try {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            DataOutputStream daos = new DataOutputStream(baos);
+
+            daos.writeInt(OPEN_SSL); // session type ID
+
+            // Session data.
+            byte[] data = sslSession.getEncoded();
+            daos.writeInt(data.length);
+            daos.write(data);
+
+            // Certificates.
+            X509Certificate[] certs = session.getPeerCertificateChain();
+            daos.writeInt(certs.length);
+
+            // TODO: Call nativegetpeercertificates()
+            for (X509Certificate cert : certs) {
+                data = cert.getEncoded();
+                daos.writeInt(data.length);
+                daos.write(data);
+            }
+
+            return baos.toByteArray();
+        } catch (IOException e) {
+            log(e);
+            return null;
+        } catch (CertificateEncodingException e) {
+            log(e);
+            return null;
+        }
+    }
+
+    /**
+     * Creates a session from the given bytes.
+     *
+     * @return a session or null if the session can't be converted
+     */
+    SSLSession toSession(byte[] data, String host, int port) {
+        ByteArrayInputStream bais = new ByteArrayInputStream(data);
+        DataInputStream dais = new DataInputStream(bais);
+        try {
+            int type = dais.readInt();
+            if (type != OPEN_SSL) {
+                log(new AssertionError("Unexpected type ID: " + type));
+                return null;
+            }
+
+            int length = dais.readInt();
+            byte[] sessionData = new byte[length];
+            dais.readFully(sessionData);
+
+            int count = dais.readInt();
+            X509Certificate[] certs = new X509Certificate[count];
+            for (int i = 0; i < count; i++) {
+                length = dais.readInt();
+                byte[] certData = new byte[length];
+                dais.readFully(certData);
+                certs[i] = X509Certificate.getInstance(certData);
+            }
+
+            return new OpenSSLSessionImpl(sessionData, parameters, host, port,
+                    certs, this);
+        } catch (IOException e) {
+            log(e);
+            return null;
+        } catch (CertificateException e) {
+            log(e);
+            return null;
+        }
+    }
+
+    static void log(Throwable t) {
+        java.util.logging.Logger.global.log(Level.WARNING,
+                "Error converting session.", t);
+    }
+
+    /**
+     * Byte array wrapper. Implements equals() and hashCode().
+     */
+    static class ByteArray {
+
+        private final byte[] bytes;
+
+        ByteArray(byte[] bytes) {
+            this.bytes = bytes;
+        }
+
+        @Override
+        public int hashCode() {
+            return Arrays.hashCode(bytes);
+        }
+
+        @Override
+        @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
+        public boolean equals(Object o) {
+            ByteArray other = (ByteArray) o;
+            return Arrays.equals(bytes, other.bytes);
+        }
+    }
+}
\ No newline at end of file
diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientHandshakeImpl.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientHandshakeImpl.java
index 8419096..55a06f5 100644
--- a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientHandshakeImpl.java
+++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientHandshakeImpl.java
@@ -613,22 +613,18 @@
             host = engineOwner.getPeerHost();
             port = engineOwner.getPeerPort();
         }
-        // END android-changed
         if (host == null || port == -1) {
             return null; // starts new session
         }
         
-        byte[] id;
-        SSLSession ses;
-        SSLSessionContext context = parameters.getClientSessionContext();
-        for (Enumeration en = context.getIds(); en.hasMoreElements();) {
-            id = (byte[])en.nextElement();
-            ses = context.getSession(id);
-            if (host.equals(ses.getPeerHost()) && port == ses.getPeerPort()) {
-                return (SSLSessionImpl)((SSLSessionImpl)ses).clone(); // resume
-            }            
+        ClientSessionContext context = parameters.getClientSessionContext();
+        SSLSessionImpl session
+                = (SSLSessionImpl) context.getSession(host, port);
+        if (session != null) {
+            session = (SSLSessionImpl) session.clone();
         }
-        return null; // starts new session
+        return session;
+        // END android-changed
     }
 
 }
diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientSessionContext.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientSessionContext.java
new file mode 100644
index 0000000..2c8738f
--- /dev/null
+++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientSessionContext.java
@@ -0,0 +1,224 @@
+/*
+ * 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 org.apache.harmony.xnet.provider.jsse;
+
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import javax.net.ssl.SSLSession;
+
+/**
+ * Caches client sessions. Indexes by host and port. Users are typically
+ * looking to reuse any session for a given host and port. Users of the
+ * standard API are forced to iterate over the sessions semi-linearly as
+ * opposed to in constant time.
+ */
+public class ClientSessionContext extends AbstractSessionContext {
+
+    /*
+     * We don't care about timeouts in the client implementation. Trying
+     * to reuse an expired session and having to start a new one requires no
+     * more effort than starting a new one, so you might as well try to reuse
+     * one on the off chance it's still valid.
+     */
+
+    /** Sessions indexed by host and port in access order. */
+    final Map<HostAndPort, SSLSession> sessions
+            = new LinkedHashMap<HostAndPort, SSLSession>() {
+        @Override
+        protected boolean removeEldestEntry(
+                Map.Entry<HostAndPort, SSLSession> eldest) {
+            // Called while lock is held on sessions.
+            boolean remove = maximumSize > 0 && size() > maximumSize;
+            if (remove) {
+                removeById(eldest.getValue());
+            }
+            return remove;
+        }
+    };
+
+    /**
+     * Sessions indexed by ID. Initialized on demand. Protected from concurrent
+     * access by holding a lock on sessions.
+     */
+    Map<ByteArray, SSLSession> sessionsById;
+
+    final SSLClientSessionCache persistentCache;
+
+    public ClientSessionContext(SSLParameters parameters,
+            SSLClientSessionCache persistentCache) {
+        super(parameters, 10, 0);
+        this.persistentCache = persistentCache;
+    }
+
+    public final void setSessionTimeout(int seconds)
+            throws IllegalArgumentException {
+        if (seconds < 0) {
+            throw new IllegalArgumentException("seconds < 0");
+        }
+        timeout = seconds;
+    }
+
+    Iterator<SSLSession> sessionIterator() {
+        synchronized (sessions) {
+            SSLSession[] array = sessions.values().toArray(
+                    new SSLSession[sessions.size()]);
+            return Arrays.asList(array).iterator();
+        }
+    }
+
+    void trimToSize() {
+        synchronized (sessions) {
+            int size = sessions.size();
+            if (size > maximumSize) {
+                int removals = size - maximumSize;
+                Iterator<SSLSession> i = sessions.values().iterator();
+                do {
+                    removeById(i.next());
+                    i.remove();
+                } while (--removals > 0);                
+            }
+        }
+    }
+
+    void removeById(SSLSession session) {
+        if (sessionsById != null) {
+            sessionsById.remove(new ByteArray(session.getId()));
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @see #getSession(String, int) for an implementation-specific but more
+     *  efficient approach
+     */
+    public SSLSession getSession(byte[] sessionId) {
+        /*
+         * This method is typically used in conjunction with getIds() to
+         * iterate over the sessions linearly, so it doesn't make sense for
+         * it to impact access order.
+         *
+         * It also doesn't load sessions from the persistent cache as doing
+         * so would likely force every session to load.
+         */
+
+        ByteArray id = new ByteArray(sessionId);
+        synchronized (sessions) {
+            indexById();
+            return sessionsById.get(id);
+        }
+    }
+
+    /**
+     * Ensures that the ID-based index is initialized.
+     */
+    private void indexById() {
+        if (sessionsById == null) {
+            sessionsById = new HashMap<ByteArray, SSLSession>();
+            for (SSLSession session : sessions.values()) {
+                sessionsById.put(new ByteArray(session.getId()), session);
+            }
+        }
+    }
+
+    /**
+     * Adds the given session to the ID-based index if the index has already
+     * been initialized.
+     */
+    private void indexById(SSLSession session) {
+        if (sessionsById != null) {
+            sessionsById.put(new ByteArray(session.getId()), session);
+        }
+    }
+
+    /**
+     * Finds a cached session for the given host name and port.
+     *
+     * @param host of server
+     * @param port of server
+     * @return cached session or null if none found
+     */
+    public SSLSession getSession(String host, int port) {
+        synchronized (sessions) {
+            SSLSession session = sessions.get(new HostAndPort(host, port));
+            if (session != null) {
+                return session;
+            }
+        }
+
+        // Look in persistent cache.
+        if (persistentCache != null) {
+            byte[] data = persistentCache.getSessionData(host, port);
+            if (data != null) {
+                SSLSession session = toSession(data, host, port);
+                if (session != null) {
+                    synchronized (sessions) {
+                        sessions.put(new HostAndPort(host, port), session);
+                        indexById(session);
+                    }
+                    return session;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    void putSession(SSLSession session) {
+        HostAndPort key = new HostAndPort(session.getPeerHost(),
+                session.getPeerPort());
+        synchronized (sessions) {
+            sessions.put(key, session);
+            indexById(session);
+        }
+
+        // TODO: This in a background thread.
+        if (persistentCache != null) {
+            byte[] data = toBytes(session);
+            if (data != null) {
+                persistentCache.putSessionData(session, data);
+            }
+        }
+    }
+
+    static class HostAndPort {
+        final String host;
+        final int port;
+
+        HostAndPort(String host, int port) {
+            this.host = host;
+            this.port = port;
+        }
+
+        @Override
+        public int hashCode() {
+            return host.hashCode() * 31 + port;
+        }
+
+        @Override
+        @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
+        public boolean equals(Object o) {
+            HostAndPort other = (HostAndPort) o;
+            return host.equals(other.host) && port == other.port;
+        }
+    }
+}
diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/FileClientSessionCache.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/FileClientSessionCache.java
new file mode 100644
index 0000000..ab097e4
--- /dev/null
+++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/FileClientSessionCache.java
@@ -0,0 +1,374 @@
+/*
+ * 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 org.apache.harmony.xnet.provider.jsse;
+
+import javax.net.ssl.SSLSession;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.Iterator;
+import java.util.Arrays;
+import java.util.logging.Level;
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * File-based cache implementation. Only one process should access the
+ * underlying directory at a time.
+ */
+public class FileClientSessionCache {
+
+    static final int MAX_SIZE = 20;
+
+    static final java.util.logging.Logger logger
+            = java.util.logging.Logger.getLogger(
+                    FileClientSessionCache.class.getName());
+
+    private FileClientSessionCache() {}
+
+    /**
+     * This cache creates one file per SSL session using "host.port" for
+     * the file name. Files are created or replaced when session data is put
+     * in the cache (see {@link #putSessionData}). Files are read on
+     * cache hits, but not on cache misses.
+     *
+     * <p>When the number of session files exceeds MAX_SIZE, we delete the
+     * least-recently-used file. We don't current persist the last access time,
+     * so the ordering actually ends up being least-recently-modified in some
+     * cases and even just "not accessed in this process" if the filesystem
+     * doesn't track last modified times.
+     */
+    static class Impl implements SSLClientSessionCache {
+
+        /** Directory to store session files in. */
+        final File directory;
+
+        /**
+         * Map of name -> File. Keeps track of the order files were accessed in.
+         */
+        Map<String, File> accessOrder = newAccessOrder();
+
+        /** The number of files on disk. */
+        int size;
+
+        /**
+         * The initial set of files. We use this to defer adding information
+         * about all files to accessOrder until necessary.
+         */
+        String[] initialFiles;
+
+        /**
+         * Constructs a new cache backed by the given directory.
+         */
+        Impl(File directory) throws IOException {
+            boolean exists = directory.exists();
+            if (exists && !directory.isDirectory()) {
+                throw new IOException(directory
+                        + " exists but is not a directory.");
+            }
+
+            if (exists) {
+                // Read and sort initial list of files. We defer adding
+                // information about these files to accessOrder until necessary
+                // (see indexFiles()). Sorting the list enables us to detect
+                // cache misses in getSessionData().
+                // Note: Sorting an array here was faster than creating a
+                // HashSet on Dalvik.
+                initialFiles = directory.list();
+                Arrays.sort(initialFiles);
+                size = initialFiles.length;
+            } else {
+                // Create directory.
+                if (!directory.mkdirs()) {
+                    throw new IOException("Creation of " + directory
+                            + " directory failed.");
+                }
+                size = 0;
+            }
+
+            this.directory = directory;
+        }
+
+        /**
+         * Creates a new access-ordered linked hash map.
+         */
+        private static Map<String, File> newAccessOrder() {
+            return new LinkedHashMap<String, File>(
+                    MAX_SIZE, 0.75f, true /* access order */);
+        }
+
+        /**
+         * Gets the file name for the given host and port.
+         */
+        private static String fileName(String host, int port) {
+            if (host == null) {
+                throw new NullPointerException("host");
+            }
+            return host + "." + port;
+        }
+
+        public synchronized byte[] getSessionData(String host, int port) {
+            /*
+             * Note: This method is only called when the in-memory cache
+             * in SSLSessionContext misses, so it would be unnecesarily
+             * rendundant for this cache to store data in memory.
+             */
+
+            String name = fileName(host, port);
+            File file = accessOrder.get(name);
+
+            if (file == null) {
+                // File wasn't in access order. Check initialFiles...
+                if (initialFiles == null) {
+                    // All files are in accessOrder, so it doesn't exist.
+                    return null;
+                }
+
+                // Look in initialFiles.
+                if (Arrays.binarySearch(initialFiles, name) < 0) {
+                    // Not found.
+                    return null;
+                }
+
+                // The file is on disk but not in accessOrder yet.
+                file = new File(directory, name);
+                accessOrder.put(name, file);
+            }
+
+            FileInputStream in;
+            try {
+                in = new FileInputStream(file);
+            } catch (FileNotFoundException e) {
+                logReadError(host, e);
+                return null;
+            }
+            try {
+                int size = (int) file.length();
+                byte[] data = new byte[size];
+                new DataInputStream(in).readFully(data);
+                logger.log(Level.FINE, "Read session for " + host + ".");
+                return data;
+            } catch (IOException e) {
+                logReadError(host, e);
+                return null;
+            } finally {
+                try {
+                    in.close();
+                } catch (IOException e) { /* ignore */ }
+            }
+        }
+
+        static void logReadError(String host, Throwable t) {
+            logger.log(Level.INFO, "Error reading session data for " + host
+                    + ".", t);
+        }
+
+        public synchronized void putSessionData(SSLSession session,
+                byte[] sessionData) {
+            String host = session.getPeerHost();
+            if (sessionData == null) {
+                throw new NullPointerException("sessionData");
+            }
+
+            String name = fileName(host, session.getPeerPort());
+            File file = new File(directory, name);
+
+            // Used to keep track of whether or not we're expanding the cache.
+            boolean existedBefore = file.exists();
+
+            FileOutputStream out;
+            try {
+                out = new FileOutputStream(file);
+            } catch (FileNotFoundException e) {
+                // We can't write to the file.
+                logWriteError(host, e);
+                return;
+            }
+
+            // If we expanded the cache (by creating a new file)...
+            if (!existedBefore) {
+                size++;
+
+                // Delete an old file if necessary.
+                makeRoom();
+            }
+
+            boolean writeSuccessful = false;
+            try {
+                out.write(sessionData);
+                writeSuccessful = true;
+            } catch (IOException e) {
+                logWriteError(host, e);
+            } finally {
+                boolean closeSuccessful = false;
+                try {
+                    out.close();
+                    closeSuccessful = true;
+                } catch (IOException e) {
+                    logWriteError(host, e);
+                } finally {
+                    if (!writeSuccessful || !closeSuccessful) {
+                        // Storage failed. Clean up.
+                        delete(file);
+                    } else {
+                        // Success!
+                        accessOrder.put(name, file);
+                        logger.log(Level.FINE, "Stored session for " + host
+                                + ".");
+                    }
+                }
+            }
+        }
+
+        /**
+         * Deletes old files if necessary.
+         */
+        private void makeRoom() {
+            if (size <= MAX_SIZE) {
+                return;
+            }
+
+            indexFiles();
+
+            // Delete LRUed files.
+            int removals = size - MAX_SIZE;
+            Iterator<File> i = accessOrder.values().iterator();
+            do {
+                delete(i.next());
+                i.remove();
+            } while (--removals > 0);
+        }
+
+        /**
+         * Lazily updates accessOrder to know about all files as opposed to
+         * just the files accessed since this process started.
+         */
+        private void indexFiles() {
+            String[] initialFiles = this.initialFiles;
+            if (initialFiles != null) {
+                this.initialFiles = null;
+
+                // Files on disk only, sorted by last modified time.
+                // TODO: Use last access time.
+                Set<CacheFile> diskOnly = new TreeSet<CacheFile>();
+                for (String name : initialFiles) {
+                    // If the file hasn't been accessed in this process...
+                    if (!accessOrder.containsKey(name)) {
+                        diskOnly.add(new CacheFile(directory, name));
+                    }
+                }
+
+                if (!diskOnly.isEmpty()) {
+                    // Add files not accessed in this process to the beginning
+                    // of accessOrder.
+                    Map<String, File> newOrder = newAccessOrder();
+                    for (CacheFile cacheFile : diskOnly) {
+                        newOrder.put(cacheFile.name, cacheFile);
+                    }
+                    newOrder.putAll(accessOrder);
+    
+                    this.accessOrder = newOrder;
+                }
+            }
+        }
+
+        @SuppressWarnings("ThrowableInstanceNeverThrown")
+        private void delete(File file) {
+            if (!file.delete()) {
+                logger.log(Level.INFO, "Failed to delete " + file + ".",
+                        new IOException());
+            }
+            size--;
+        }
+
+        static void logWriteError(String host, Throwable t) {
+            logger.log(Level.INFO, "Error writing session data for "
+                    + host + ".", t);
+        }
+    }
+
+    /**
+     * Maps directories to the cache instances that are backed by those
+     * directories. We synchronize access using the cache instance, so it's
+     * important that everyone shares the same instance.
+     */
+    static final Map<File, FileClientSessionCache.Impl> caches
+            = new HashMap<File, FileClientSessionCache.Impl>();
+
+    /**
+     * Returns a cache backed by the given directory. Creates the directory
+     * (including parent directories) if necessary. This cache should have
+     * exclusive access to the given directory.
+     *
+     * @param directory to store files in
+     * @return a cache backed by the given directory
+     * @throws IOException if the file exists and is not a directory or if
+     *  creating the directories fails
+     */
+    public static synchronized SSLClientSessionCache usingDirectory(
+            File directory) throws IOException {
+        FileClientSessionCache.Impl cache = caches.get(directory);
+        if (cache == null) {
+            cache = new FileClientSessionCache.Impl(directory);
+            caches.put(directory, cache);
+        }
+        return cache;
+    }
+
+    /** For testing. */
+    static synchronized void reset() {
+        caches.clear();
+    }
+
+    /** A file containing a piece of cached data. */
+    static class CacheFile extends File {
+
+        final String name;
+
+        CacheFile(File dir, String name) {
+            super(dir, name);
+            this.name = name;
+        }
+
+        long lastModified = -1;
+
+        @Override
+        public long lastModified() {
+            long lastModified = this.lastModified;
+            if (lastModified == -1) {
+                lastModified = this.lastModified = super.lastModified();
+            }
+            return lastModified;
+        }
+
+        @Override
+        public int compareTo(File another) {
+            // Sort by last modified time.
+            long result = lastModified() - another.lastModified();
+            if (result == 0) {
+                return super.compareTo(another);
+            }
+            return result < 0 ? -1 : 1;
+        }
+    }
+}
\ No newline at end of file
diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketImpl.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketImpl.java
index 4fc6e99..e985908 100644
--- a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketImpl.java
+++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketImpl.java
@@ -302,7 +302,8 @@
 
     @Override
     public Socket accept() throws IOException {
-        OpenSSLSocketImpl socket = new OpenSSLSocketImpl(sslParameters, ssl_op_no);
+        OpenSSLSocketImpl socket
+                = new OpenSSLSocketImpl(sslParameters, ssl_op_no);
         implAccept(socket);
         socket.accept(ssl_ctx, client_mode);
 
diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl.java
index 475d388..ca7d6f8 100644
--- a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl.java
+++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl.java
@@ -23,6 +23,7 @@
 import java.security.cert.Certificate;
 import java.security.cert.X509Certificate;
 import java.util.Iterator;
+import java.util.UnknownFormatConversionException;
 import java.util.Vector;
 
 import javax.net.ssl.SSLPeerUnverifiedException;
@@ -55,21 +56,41 @@
     private SSLParameters sslParameters;
     private String peerHost;
     private int peerPort;
+    private final SSLSessionContext sessionContext;
 
     /**
      * Class constructor creates an SSL session context given the appropriate
      * SSL parameters.
+     *
+     * @param session the Identifier for SSL session
      * @param sslParameters the SSL parameters like ciphers' suites etc.
-     * @param ssl the Identifier for SSL session
      */
-    protected OpenSSLSessionImpl(int session, SSLParameters sslParameters, String peerHost, int peerPort) {
+    protected OpenSSLSessionImpl(int session, SSLParameters sslParameters,
+            String peerHost, int peerPort, SSLSessionContext sessionContext) {
         this.session = session;
         this.sslParameters = sslParameters;
         this.peerHost = peerHost;
         this.peerPort = peerPort;
+        this.sessionContext = sessionContext;
     }
 
     /**
+     * Constructs a session from a byte[].
+     */
+    OpenSSLSessionImpl(byte[] derData, SSLParameters sslParameters,
+            String peerHost, int peerPort,
+            javax.security.cert.X509Certificate[] peerCertificateChain,
+            SSLSessionContext sessionContext)
+            throws IOException {
+        this.sslParameters = sslParameters;
+        this.peerHost = peerHost;
+        this.peerPort = peerPort;
+        this.peerCertificateChain = peerCertificateChain;
+        this.sessionContext = sessionContext;
+        initializeNative(derData);
+    }
+  
+    /**
      * Returns the identifier of the actual OpenSSL session.
      */
     private native byte[] nativegetid();
@@ -90,6 +111,40 @@
     private native long nativegetcreationtime();
 
     /**
+     * Serialize the native state of the session ( ID, cypher, keys - but 
+     * not certs ), using openSSL i2d_SSL_SESSION()
+     * 
+     * @return the DER encoding of the session. 
+     */
+    private native byte[] nativeserialize();
+    
+    /**
+     * Create a SSL_SESSION object using d2i_SSL_SESSION.
+     */
+    private native int nativedeserialize(byte[] data, int size);
+
+    /**
+     * Get the session object in DER format. This allows saving the session
+     * data or sharing it with other processes.  
+     */
+    byte[] getEncoded() {
+      return nativeserialize();
+    }
+
+    /**
+     * Init the underlying native object from DER data. This 
+     * allows loading the saved session.
+     * @throws IOException 
+     */
+    private void initializeNative(byte[] derData) throws IOException {
+      this.session = nativedeserialize(derData, derData.length);
+      if (this.session == 0) { 
+        throw new IOException("Invalid session data");
+      }
+    }
+    
+    
+    /**
      * Gets the creation time of the SSL session.
      * @return the session's creation time in milli seconds since 12.00 PM,
      * January 1st, 1970
@@ -337,7 +392,7 @@
         if (sm != null) {
             sm.checkPermission(new SSLPermission("getSSLSessionContext"));
         }
-        return sslParameters.getClientSessionContext();
+        return sessionContext;
     }
 
     /**
@@ -347,7 +402,7 @@
      * @return true if this session may be resumed.
      */
     public boolean isValid() {
-        SSLSessionContextImpl context = sslParameters.getClientSessionContext();
+        SSLSessionContext context = sessionContext;
         if (isValid
                 && context != null
                 && context.getSessionTimeout() != 0
@@ -372,7 +427,7 @@
      * of security, by the full machinery of the <code>AccessController</code>
      * class.
      *
-     * @param <code>String name</code> the name of the binding to find.
+     * @param name the name of the binding to find.
      * @return the value bound to that name, or null if the binding does not
      *         exist.
      * @throws <code>IllegalArgumentException</code> if the argument is null.
@@ -416,9 +471,9 @@
      * -data bounds are monitored, as a matter of security, by the full
      * machinery of the <code>AccessController</code> class.
      *
-     * @param <code>String name</code> the name of the link (no null are
+     * @param name the name of the link (no null are
      *            accepted!)
-     * @param <code>Object value</code> data object that shall be bound to
+     * @param value data object that shall be bound to
      *            name.
      * @throws <code>IllegalArgumentException</code> if one or both
      *             argument(s) is null.
@@ -444,7 +499,7 @@
      * monitored, as a matter of security, by the full machinery of the
      * <code>AccessController</code> class.
      *
-     * @param <code>String name</code> the name of the link (no null are
+     * @param name the name of the link (no null are
      *            accepted!)
      * @throws <code>IllegalArgumentException</code> if the argument is null.
      */
diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl.java
index 1d38ca9..8779736 100644
--- a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl.java
+++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl.java
@@ -21,7 +21,6 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
-import java.lang.reflect.Method;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.Socket;
@@ -31,7 +30,6 @@
 import java.security.cert.X509Certificate;
 import java.security.interfaces.RSAPublicKey;
 import java.util.ArrayList;
-import java.util.Enumeration;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -58,7 +56,7 @@
     private int ssl;
     private InputStream is;
     private OutputStream os;
-    private Object handshakeLock = new Object();
+    private final Object handshakeLock = new Object();
     private Object readLock = new Object();
     private Object writeLock = new Object();
     private SSLParameters sslParameters;
@@ -66,7 +64,7 @@
     private Socket socket;
     private boolean autoClose;
     private boolean handshakeStarted = false;
-    private ArrayList listeners;
+    private ArrayList<HandshakeCompletedListener> listeners;
     private long ssl_op_no = 0x00000000L;
     private int timeout = 0;
     private InetSocketAddress address;
@@ -137,11 +135,11 @@
     /**
      * Class constructor with 2 parameters
      *
-     * @param <code>SSLParameters sslParameters</code> Parameters for the SSL
+     * @param sslParameters Parameters for the SSL
      *            context
-     * @param <code>long ssl_op_no</code> Parameter to set the enabled
+     * @param ssl_op_no Parameter to set the enabled
      *            protocols
-     * @throws <code>IOException</code> if network fails
+     * @throws IOException if network fails
      */
     protected OpenSSLSocketImpl(SSLParameters sslParameters, long ssl_op_no) throws IOException {
         super();
@@ -153,9 +151,9 @@
     /**
      * Class constructor with 1 parameter
      *
-     * @param <code>SSLParameters sslParameters</code> Parameters for the SSL
+     * @param sslParameters Parameters for the SSL
      *            context
-     * @throws <code>IOException</code> if network fails
+     * @throws IOException if network fails
      */
     protected OpenSSLSocketImpl(SSLParameters sslParameters) throws IOException {
         super();
@@ -167,11 +165,8 @@
     /**
      * Class constructor with 3 parameters
      *
-     * @param <code> String host</code>
-     * @param <code>int port</code>
-     * @param <code>SSLParameters sslParameters</code>
-     * @throws <code>IOException</code> if network fails
-     * @throws <code>UnknownHostException</code> host not defined
+     * @throws IOException if network fails
+     * @throws java.net.UnknownHostException host not defined
      */
     protected OpenSSLSocketImpl(String host, int port,
             SSLParameters sslParameters)
@@ -186,11 +181,8 @@
     /**
      * Class constructor with 3 parameters: 1st is InetAddress
      *
-     * @param <code>InetAddress address</code>
-     * @param <code>int port</code>
-     * @param <code>SSLParameters sslParameters</code>
-     * @throws <code>IOException</code> if network fails
-     * @throws <code>UnknownHostException</code> host not defined
+     * @throws IOException if network fails
+     * @throws java.net.UnknownHostException host not defined
      */
     protected OpenSSLSocketImpl(InetAddress address, int port,
             SSLParameters sslParameters)
@@ -205,13 +197,8 @@
     /**
      * Class constructor with 5 parameters: 1st is host
      *
-     * @param <code>String host</code>
-     * @param <code>int port</code>
-     * @param <code>InetAddress localHost</code>
-     * @param <code>int localPort</code>
-     * @param <code>SSLParameters sslParameters</code>
-     * @throws <code>IOException</code> if network fails
-     * @throws <code>UnknownHostException</code> host not defined
+     * @throws IOException if network fails
+     * @throws java.net.UnknownHostException host not defined
      */
     protected OpenSSLSocketImpl(String host, int port, InetAddress clientAddress,
             int clientPort, SSLParameters sslParameters)
@@ -225,13 +212,8 @@
     /**
      * Class constructor with 5 parameters: 1st is InetAddress
      *
-     * @param <code>InetAddress address</code>
-     * @param <code>int port</code>
-     * @param <code>InetAddress localAddress</code>
-     * @param <code>int localPort</code>
-     * @param <code>SSLParameters sslParameters</code>
-     * @throws <code>IOException</code> if network fails
-     * @throws <code>UnknownHostException</code> host not defined
+     * @throws IOException if network fails
+     * @throws java.net.UnknownHostException host not defined
      */
     protected OpenSSLSocketImpl(InetAddress address, int port,
             InetAddress clientAddress, int clientPort, SSLParameters sslParameters)
@@ -246,12 +228,7 @@
      * Constructor with 5 parameters: 1st is socket. Enhances an existing socket
      * with SSL functionality.
      *
-     * @param <code>Socket socket</code>
-     * @param <code>String host</code>
-     * @param <code>int port</code>
-     * @param <code>boolean autoClose</code>
-     * @param <code>SSLParameters sslParameters</code>
-     * @throws <code>IOException</code> if network fails
+     * @throws IOException if network fails
      */
     protected OpenSSLSocketImpl(Socket socket, String host, int port,
             boolean autoClose, SSLParameters sslParameters) throws IOException {
@@ -278,26 +255,26 @@
      *
      * @return OpenSSLSessionImpl
      */
-    private OpenSSLSessionImpl getOpenSSLSessionImpl() {
-        try {
-            byte[] id;
-            SSLSession ses;
-            for (Enumeration<byte[]> en = sslParameters.getClientSessionContext().getIds(); en.hasMoreElements();) {
-                id = en.nextElement();
-                ses = sslParameters.getClientSessionContext().getSession(id);
-                if (ses instanceof OpenSSLSessionImpl && ses.isValid() &&
-                        super.getInetAddress() != null &&
-                        super.getInetAddress().getHostAddress() != null &&
-                        super.getInetAddress().getHostName().equals(ses.getPeerHost()) &&
-                        super.getPort() == ses.getPeerPort()) {
-                        return (OpenSSLSessionImpl) ses;
-                }
-            }
-        } catch (Exception ex) {
-            // It's not clear to me under what circumstances the above code
-            // might fail. I also can't reproduce it.
+    private OpenSSLSessionImpl getCachedClientSession() {
+        if (super.getInetAddress() == null ||
+                super.getInetAddress().getHostAddress() == null ||
+                super.getInetAddress().getHostName() == null) {
+            return null;
         }
-        return null;
+        ClientSessionContext sessionContext
+                = sslParameters.getClientSessionContext();
+        return (OpenSSLSessionImpl) sessionContext.getSession(
+                super.getInetAddress().getHostName(),
+                super.getPort());
+    }
+
+    /**
+     * Ensures that logger is lazily loaded. The outer class seems to load
+     * before logging is ready.
+     */
+    static class LoggerHolder {
+        static final Logger logger = Logger.getLogger(
+                OpenSSLSocketImpl.class.getName());
     }
 
     /**
@@ -317,38 +294,60 @@
                 return;
             }
         }
-        
-        {
-            // Debug
-            int size = 0;
-            for (Enumeration<byte[]> en = sslParameters.getClientSessionContext().getIds();
-                    en.hasMoreElements(); en.nextElement()) { size++; };
-        }
-        OpenSSLSessionImpl session = getOpenSSLSessionImpl();
+
+        OpenSSLSessionImpl session = getCachedClientSession();
 
         // Check if it's allowed to create a new session (default is true)
-        if (!sslParameters.getEnableSessionCreation() && session == null) {
+        if (session == null && !sslParameters.getEnableSessionCreation()) {
             throw new SSLHandshakeException("SSL Session may not be created");
         } else {
-            if (nativeconnect(ssl_ctx, this.socket != null ?
-                    this.socket : this, sslParameters.getUseClientMode(), session != null ? session.session : 0)) {
+            Socket socket = this.socket != null ? this.socket : this;
+            int sessionId = session != null ? session.session : 0;
+            if (nativeconnect(ssl_ctx, socket, sslParameters.getUseClientMode(),
+                    sessionId)) {
+                // nativeconnect shouldn't return true if the session is not
+                // done
                 session.lastAccessedTime = System.currentTimeMillis();
                 sslSession = session;
-            } else {
-                if (address == null) sslSession = new OpenSSLSessionImpl(nativegetsslsession(ssl),
-                        sslParameters, super.getInetAddress().getHostName(), super.getPort());
-                else sslSession = new OpenSSLSessionImpl(nativegetsslsession(ssl),
-                        sslParameters, address.getHostName(), address.getPort());
-                try {
-                    X509Certificate[] peerCertificates = (X509Certificate[]) sslSession.getPeerCertificates();
 
-                    if (peerCertificates == null || peerCertificates.length == 0) {
+                LoggerHolder.logger.fine("Reused cached session for "
+                        + getInetAddress().getHostName() + ".");
+            } else {
+                if (session != null) {
+                    LoggerHolder.logger.fine("Reuse of cached session for "
+                            + getInetAddress().getHostName() + " failed.");
+                } else {
+                    LoggerHolder.logger.fine("Created new session for "
+                            + getInetAddress().getHostName() + ".");
+                }
+
+                ClientSessionContext sessionContext
+                        = sslParameters.getClientSessionContext();
+                if (address == null) {
+                    sslSession = new OpenSSLSessionImpl(
+                            nativegetsslsession(ssl), sslParameters,
+                            super.getInetAddress().getHostName(),
+                            super.getPort(), sessionContext);
+                } else  {
+                    sslSession = new OpenSSLSessionImpl(
+                            nativegetsslsession(ssl), sslParameters,
+                            address.getHostName(), address.getPort(),
+                            sessionContext);
+                }
+
+                try {
+                    X509Certificate[] peerCertificates = (X509Certificate[])
+                            sslSession.getPeerCertificates();
+
+                    if (peerCertificates == null
+                            || peerCertificates.length == 0) {
                         throw new SSLException("Server sends no certificate");
                     }
 
-                    sslParameters.getTrustManager().checkServerTrusted(peerCertificates,
-                                                                       nativecipherauthenticationmethod());
-                    sslParameters.getClientSessionContext().putSession(sslSession);
+                    sslParameters.getTrustManager().checkServerTrusted(
+                            peerCertificates,
+                            nativecipherauthenticationmethod());
+                    sessionContext.putSession(sslSession);
                 } catch (CertificateException e) {
                     throw new SSLException("Not trusted server certificate", e);
                 }
@@ -360,9 +359,8 @@
             HandshakeCompletedEvent event =
                 new HandshakeCompletedEvent(this, sslSession);
             int size = listeners.size();
-            for (int i=0; i<size; i++) {
-                ((HandshakeCompletedListener)listeners.get(i))
-                    .handshakeCompleted(event);
+            for (int i = 0; i < size; i++) {
+                listeners.get(i).handshakeCompleted(event);
             }
         }
     }
@@ -381,22 +379,27 @@
 
         nativeaccept(this, m_ctx, client_mode);
 
+        ServerSessionContext sessionContext
+                = sslParameters.getServerSessionContext();
         sslSession = new OpenSSLSessionImpl(nativegetsslsession(ssl),
-                sslParameters, super.getInetAddress().getHostName(), super.getPort());
+                sslParameters, super.getInetAddress().getHostName(),
+                super.getPort(), sessionContext);
         sslSession.lastAccessedTime = System.currentTimeMillis();
+        sessionContext.putSession(sslSession);
     }
 
     /**
      * Callback methode for the OpenSSL native certificate verification process.
      *
-     * @param <code>byte[][] bytes</code> Byte array containing the cert's
+     * @param bytes Byte array containing the cert's
      *            information.
      * @return 0 if the certificate verification fails or 1 if OK
      */
     @SuppressWarnings("unused")
     private int verify_callback(byte[][] bytes) {
         try {
-            X509Certificate[] peerCertificateChain = new X509Certificate[bytes.length];
+            X509Certificate[] peerCertificateChain
+                    = new X509Certificate[bytes.length];
             for(int i = 0; i < bytes.length; i++) {
                 peerCertificateChain[i] =
                     new X509CertImpl(javax.security.cert.X509Certificate.getInstance(bytes[i]).getEncoded());
@@ -582,7 +585,6 @@
     /**
      * Registers a listener to be notified that a SSL handshake
      * was successfully completed on this connection.
-     * @param <code>HandShakeCompletedListener listener</code>
      * @throws <code>IllegalArgumentException</code> if listener is null.
      */
     public void addHandshakeCompletedListener(
@@ -598,7 +600,6 @@
 
     /**
      * The method removes a registered listener.
-     * @param <code>HandShakeCompletedListener listener</code>
      * @throws IllegalArgumentException if listener is null or not registered
      */
     public void removeHandshakeCompletedListener(
@@ -631,7 +632,7 @@
      * SSL sessions. If the flag is set to false, and there are no actual
      * sessions to resume, then there will be no successful handshaking.
      *
-     * @param <code>boolean flag</code> true if session may be created; false
+     * @param flag true if session may be created; false
      *            if a session already exists and must be resumed.
      */
     public void setEnableSessionCreation(boolean flag) {
@@ -683,9 +684,9 @@
      * This method enables the cipher suites listed by
      * getSupportedCipherSuites().
      *
-     * @param <code> String[] suites</code> names of all the cipher suites to
+     * @param suites names of all the cipher suites to
      *            put on use
-     * @throws <code>IllegalArgumentException</code> when one or more of the
+     * @throws IllegalArgumentException when one or more of the
      *             ciphers in array suites are not supported, or when the array
      *             is null.
      */
@@ -781,9 +782,9 @@
     /**
      * This method set the actual SSL socket to client mode.
      *
-     * @param <code>boolean mode</code> true if the socket starts in client
+     * @param mode true if the socket starts in client
      *            mode
-     * @throws <code>IllegalArgumentException</code> if mode changes during
+     * @throws IllegalArgumentException if mode changes during
      *             handshake.
      */
     public synchronized void setUseClientMode(boolean mode) {
@@ -818,7 +819,7 @@
      * Sets the SSL socket to use client's authentication. Relevant only for
      * server sockets!
      *
-     * @param <code>boolean need</code> true if client authentication is
+     * @param need true if client authentication is
      *            desired, false if not.
      */
     public void setNeedClientAuth(boolean need) {
@@ -831,7 +832,7 @@
      * method will continue the negotiation if the client decide not to send
      * authentication credentials.
      *
-     * @param <code>boolean want</code> true if client authentication is
+     * @param want true if client authentication is
      *            desired, false if not.
      */
     public void setWantClientAuth(boolean want) {
@@ -878,6 +879,9 @@
      *             socket's closure.
      */
     public void close() throws IOException {
+        // TODO: Close SSL sockets using a background thread so they close
+        // gracefully.
+
         synchronized (handshakeLock) {
             if (!handshakeStarted) {
                 handshakeStarted = true;
@@ -987,10 +991,11 @@
     }
 
     /**
-     * Helper class for a thread that knows how to call {@link #close} on behalf
-     * of instances being finalized, since that call can take arbitrarily long
-     * (e.g., due to a slow network), and an overly long-running finalizer will
-     * cause the process to be totally aborted.
+     * Helper class for a thread that knows how to call
+     * {@link OpenSSLSocketImpl#close} on behalf of instances being finalized,
+     * since that call can take arbitrarily long (e.g., due to a slow network),
+     * and an overly long-running finalizer will cause the process to be
+     * totally aborted.
      */
     private class Finalizer extends Thread {
         public void run() {
diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLClientSessionCache.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLClientSessionCache.java
new file mode 100644
index 0000000..8a73fa5
--- /dev/null
+++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLClientSessionCache.java
@@ -0,0 +1,53 @@
+/*
+ * 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 org.apache.harmony.xnet.provider.jsse;
+
+import javax.net.ssl.SSLSession;
+
+/**
+ * A persistent {@link javax.net.ssl.SSLSession} cache used by
+ * {@link javax.net.ssl.SSLSessionContext} to share client-side SSL sessions
+ * across processes. For example, this cache enables applications to
+ * persist and reuse sessions across restarts.
+ *
+ * <p>The {@code SSLSessionContext} implementation converts
+ * {@code SSLSession}s into raw bytes and vice versa. The exact makeup of the
+ * session data is dependent upon the caller's implementation and is opaque to
+ * the {@code SSLClientSessionCache} implementation.
+ */
+public interface SSLClientSessionCache {
+
+  /**
+   * Gets data from a pre-existing session for a given server host and port.
+   *
+   * @param host from {@link javax.net.ssl.SSLSession#getPeerHost()}
+   * @param port from {@link javax.net.ssl.SSLSession#getPeerPort()}
+   * @return the session data or null if none is cached
+   * @throws NullPointerException if host is null
+   */
+  public byte[] getSessionData(String host, int port);
+
+  /**
+   * Stores session data for the given session.
+   *
+   * @param session to cache data for
+   * @param sessionData to cache
+   * @throws NullPointerException if session, result of
+   *  {@code session.getPeerHost()} or data is null
+   */
+  public void putSessionData(SSLSession session, byte[] sessionData);
+}
\ No newline at end of file
diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLContextImpl.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLContextImpl.java
index 3f4ab96..2e4de04 100644
--- a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLContextImpl.java
+++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLContextImpl.java
@@ -22,9 +22,6 @@
 
 package org.apache.harmony.xnet.provider.jsse;
 
-// BEGIN android-removed
-// import org.apache.harmony.xnet.provider.jsse.SSLSocketFactoryImpl;
-// END android-removed
 import org.apache.harmony.xnet.provider.jsse.SSLEngineImpl;
 import org.apache.harmony.xnet.provider.jsse.SSLParameters;
 // BEGIN android-removed
@@ -42,19 +39,21 @@
 import javax.net.ssl.SSLSocketFactory;
 import javax.net.ssl.TrustManager;
 
+// BEGIN android-note
+//  Modified heavily during SSLSessionContext refactoring. Added support for
+//  persistent session caches.
+// END android-note
+
 /**
  * Implementation of SSLContext service provider interface.
  */
 public class SSLContextImpl extends SSLContextSpi {
 
-    // client session context contains the set of reusable
-    // client-side SSL sessions
-    private SSLSessionContextImpl clientSessionContext =
-        new SSLSessionContextImpl();
-    // server session context contains the set of reusable
-    // server-side SSL sessions
-    private SSLSessionContextImpl serverSessionContext =
-        new SSLSessionContextImpl();
+    /** Client session cache. */
+    private ClientSessionContext clientSessionContext;
+
+    /** Server session cache. */
+    private ServerSessionContext serverSessionContext;
 
     protected SSLParameters sslParameters;
 
@@ -64,26 +63,44 @@
 
     public void engineInit(KeyManager[] kms, TrustManager[] tms,
             SecureRandom sr) throws KeyManagementException {
-        sslParameters = new SSLParameters(kms, tms, sr, clientSessionContext,
-                serverSessionContext);
+        engineInit(kms, tms, sr, null, null);
+    }
+
+    /**
+     * Initializes this {@code SSLContext} instance. All of the arguments are
+     * optional, and the security providers will be searched for the required
+     * implementations of the needed algorithms.
+     *
+     * @param kms the key sources or {@code null}
+     * @param tms the trust decision sources or {@code null}
+     * @param sr the randomness source or {@code null}
+     * @param clientCache persistent client session cache or {@code null}
+     * @param serverCache persistent server session cache or {@code null}
+     * @throws KeyManagementException if initializing this instance fails
+     * 
+     * @since Android 1.1
+     */
+    public void engineInit(KeyManager[] kms, TrustManager[] tms,
+            SecureRandom sr, SSLClientSessionCache clientCache,
+            SSLServerSessionCache serverCache) throws KeyManagementException {
+        sslParameters = new SSLParameters(kms, tms, sr,
+                clientCache, serverCache);
+        clientSessionContext = sslParameters.getClientSessionContext();
+        serverSessionContext = sslParameters.getServerSessionContext();
     }
 
     public SSLSocketFactory engineGetSocketFactory() {
         if (sslParameters == null) {
             throw new IllegalStateException("SSLContext is not initiallized.");
         }
-        // BEGIN android-changed
         return new OpenSSLSocketFactoryImpl(sslParameters);
-        // END android-changed
     }
 
     public SSLServerSocketFactory engineGetServerSocketFactory() {
         if (sslParameters == null) {
             throw new IllegalStateException("SSLContext is not initiallized.");
         }
-        // BEGIN android-changed
         return new OpenSSLServerSocketFactoryImpl(sslParameters);
-        // END android-changed
     }
 
     public SSLEngine engineCreateSSLEngine(String host, int port) {
@@ -101,12 +118,11 @@
         return new SSLEngineImpl((SSLParameters) sslParameters.clone());
     }
 
-    public SSLSessionContext engineGetServerSessionContext() {
+    public ServerSessionContext engineGetServerSessionContext() {
         return serverSessionContext;
     }
 
-    public SSLSessionContext engineGetClientSessionContext() {
+    public ClientSessionContext engineGetClientSessionContext() {
         return clientSessionContext;
     }
-}
-
+}
\ No newline at end of file
diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLParameters.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLParameters.java
index 9a14f46..60a5535 100644
--- a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLParameters.java
+++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLParameters.java
@@ -22,8 +22,6 @@
 
 package org.apache.harmony.xnet.provider.jsse;
 
-import org.apache.harmony.xnet.provider.jsse.SSLSessionContextImpl;
-
 import java.security.KeyManagementException;
 import java.security.KeyStore;
 import java.security.KeyStoreException;
@@ -45,7 +43,9 @@
  * and controls whether new SSL sessions may be established by this
  * socket or not.
  */
-public class SSLParameters {
+// BEGIN android-changed
+public class SSLParameters implements Cloneable {
+// END android-changed
 
     // default source of authentication keys
     private static X509KeyManager defaultKeyManager;
@@ -58,10 +58,12 @@
 
     // client session context contains the set of reusable
     // client-side SSL sessions
-    private SSLSessionContextImpl clientSessionContext;
+// BEGIN android-changed
+    private final ClientSessionContext clientSessionContext;
     // server session context contains the set of reusable
     // server-side SSL sessions
-    private SSLSessionContextImpl serverSessionContext;
+    private final ServerSessionContext serverSessionContext;
+// END android-changed
     // source of authentication keys
     private X509KeyManager keyManager;
     // source of authentication trust decisions
@@ -88,16 +90,7 @@
     // if the peer with this parameters allowed to cteate new SSL session
     private boolean enable_session_creation = true;
 
-    /**
-     * Creates an instance of SSLParameters.
-     */
-    private SSLParameters() {
-        // BEGIN android-removed
-        // this.enabledCipherSuites = CipherSuite.defaultCipherSuites;
-        // END android-removed
-    }
-
-    // BEGIN android-added
+// BEGIN android-changed
     protected CipherSuite[] getEnabledCipherSuitesMember() {
         if (enabledCipherSuites == null) this.enabledCipherSuites = CipherSuite.defaultCipherSuites;
         return enabledCipherSuites;
@@ -120,23 +113,26 @@
         if (ssl_ctx == 0) ssl_ctx = nativeinitsslctx();
         return ssl_ctx;
     }
-    // END android-added
+// END android-changed
 
     /**
      * Initializes the parameters. Naturally this constructor is used
      * in SSLContextImpl.engineInit method which dirrectly passes its 
      * parameters. In other words this constructor holds all
      * the functionality provided by SSLContext.init method.
-     * See {@link javax.net.ssl.SSLContext#init(KeyManager[],TrustManager[],SecureRandom)}
-     * for more information
+     * See {@link javax.net.ssl.SSLContext#init(KeyManager[],TrustManager[],
+     * SecureRandom)} for more information
      */
     protected SSLParameters(KeyManager[] kms, TrustManager[] tms,
-            SecureRandom sr, SSLSessionContextImpl clientSessionContext,
-            SSLSessionContextImpl serverSessionContext)
+// BEGIN android-changed
+            SecureRandom sr, SSLClientSessionCache clientCache,
+            SSLServerSessionCache serverCache)
             throws KeyManagementException {
-        this();
-        this.serverSessionContext = serverSessionContext;
-        this.clientSessionContext = clientSessionContext;
+        this.serverSessionContext
+                = new ServerSessionContext(this, serverCache);
+        this.clientSessionContext
+                = new ClientSessionContext(this, clientCache);
+// END android-changed
         try {
             // initialize key manager
             boolean initialize_default = false;
@@ -228,8 +224,9 @@
 
     protected static SSLParameters getDefault() throws KeyManagementException {
         if (defaultParameters == null) {
-            defaultParameters = new SSLParameters(null, null, null,
-                    new SSLSessionContextImpl(), new SSLSessionContextImpl());
+// BEGIN android-changed
+            defaultParameters = new SSLParameters(null, null, null, null, null);
+// END android-changed
         }
         return (SSLParameters) defaultParameters.clone();
     }
@@ -237,14 +234,18 @@
     /**
      * @return server session context
      */
-    protected SSLSessionContextImpl getServerSessionContext() {
+// BEGIN android-changed
+    protected ServerSessionContext getServerSessionContext() {
+// END android-changed
         return serverSessionContext;
     }
 
     /**
      * @return client session context
      */
-    protected SSLSessionContextImpl getClientSessionContext() {
+// BEGIN android-changed
+    protected ClientSessionContext getClientSessionContext() {
+// END android-changed
         return clientSessionContext;
     }
 
@@ -335,7 +336,7 @@
 
     /**
      * Sets the set of available protocols for use in SSL connection.
-     * @param   suites: String[]
+     * @param protocols String[]
      */
     protected void setEnabledProtocols(String[] protocols) {
         if (protocols == null) {
@@ -422,23 +423,13 @@
      * @return the clone.
      */
     protected Object clone() {
-        SSLParameters parameters = new SSLParameters();
-
-        parameters.clientSessionContext = clientSessionContext;
-        parameters.serverSessionContext = serverSessionContext;
-        parameters.keyManager = keyManager;
-        parameters.trustManager = trustManager;
-        parameters.secureRandom = secureRandom;
-
-        parameters.enabledCipherSuites = enabledCipherSuites;
-        parameters.enabledProtocols = enabledProtocols;
-
-        parameters.client_mode = client_mode;
-        parameters.need_client_auth = need_client_auth;
-        parameters.want_client_auth = want_client_auth;
-        parameters.enable_session_creation = enable_session_creation;
-
-        return parameters;
+// BEGIN android-changed
+        try {
+            return super.clone();
+        } catch (CloneNotSupportedException e) {
+            throw new AssertionError(e);
+        }
+// END android-changed
     }
 }
 
diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLServerSessionCache.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLServerSessionCache.java
new file mode 100644
index 0000000..32a0e72
--- /dev/null
+++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLServerSessionCache.java
@@ -0,0 +1,52 @@
+/*
+ * 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 org.apache.harmony.xnet.provider.jsse;
+
+import javax.net.ssl.SSLSession;
+
+/**
+ * A persistent {@link javax.net.ssl.SSLSession} cache used by
+ * {@link javax.net.ssl.SSLSessionContext} to share server-side SSL sessions
+ * across processes. For example, this cache enables one server to resume
+ * a session started by a different server based on a session ID provided
+ * by the client.
+ *
+ * <p>The {@code SSLSessionContext} implementation converts
+ * {@code SSLSession}s into raw bytes and vice versa. The exact makeup of the
+ * session data is dependent upon the caller's implementation and is opaque to
+ * the {@code SSLServerSessionCache} implementation.
+ */
+public interface SSLServerSessionCache {
+
+  /**
+   * Gets the session data for given session ID.
+   *
+   * @param id from {@link javax.net.ssl.SSLSession#getId()}
+   * @return the session data or null if none is cached
+   * @throws NullPointerException if id is null
+   */
+  public byte[] getSessionData(byte[] id);
+
+  /**
+   * Stores session data for the given session.
+   *
+   * @param session to cache data for
+   * @param sessionData to cache
+   * @throws NullPointerException if session or data is null
+   */
+  public void putSessionData(SSLSession session, byte[] sessionData);
+}
\ No newline at end of file
diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSessionContextImpl.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSessionContextImpl.java
deleted file mode 100644
index 6882aa1..0000000
--- a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSessionContextImpl.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one or more
- *  contributor license agreements.  See the NOTICE file distributed with
- *  this work for additional information regarding copyright ownership.
- *  The ASF licenses this file to You 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.
- */
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * @author Boris Kuznetsov
- * @version $Revision$
- */
-package org.apache.harmony.xnet.provider.jsse;
-
-import java.nio.ByteBuffer;
-import java.util.Enumeration;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.Set;
-
-import javax.net.ssl.SSLSession;
-import javax.net.ssl.SSLSessionContext;
-
-/**
- * SSLSessionContext implementation
- * 
- * @see javax.net.ssl.SSLSessionContext
- */
-public class SSLSessionContextImpl implements SSLSessionContext {
-
-    private int cacheSize = 20;
-    private long timeout = 0;
-    private final LinkedHashMap<ByteBuffer, SSLSession> sessions =
-        new LinkedHashMap<ByteBuffer, SSLSession>(cacheSize, 0.75f, true) {
-        @Override
-        public boolean removeEldestEntry(
-                Map.Entry<ByteBuffer, SSLSession> eldest) {
-            return cacheSize > 0 && this.size() > cacheSize;
-        }
-    };
-    private volatile LinkedHashMap<ByteBuffer, SSLSession> clone =
-            new LinkedHashMap<ByteBuffer, SSLSession>();
-
-    /**
-     * @see javax.net.ssl.SSLSessionContext#getIds()
-     */
-    public Enumeration<byte[]> getIds() {
-        return new Enumeration<byte[]>() {
-            Iterator<ByteBuffer> iterator = clone.keySet().iterator();
-            public boolean hasMoreElements() {
-                return iterator.hasNext();
-            }
-            public byte[] nextElement() {
-                return iterator.next().array();
-            }
-        };
-    }
-
-    /**
-     * @see javax.net.ssl.SSLSessionContext#getSession(byte[] sessionId)
-     */
-    public SSLSession getSession(byte[] sessionId) {
-        synchronized (sessions) {
-            return sessions.get(ByteBuffer.wrap(sessionId));
-        }
-    }
-
-    /**
-     * @see javax.net.ssl.SSLSessionContext#getSessionCacheSize()
-     */
-    public int getSessionCacheSize() {
-        synchronized (sessions) {
-            return cacheSize;
-        }
-    }
-
-    /**
-     * @see javax.net.ssl.SSLSessionContext#getSessionTimeout()
-     */
-    public int getSessionTimeout() {
-        synchronized (sessions) {
-            return (int) (timeout/1000);
-        }
-    }
-
-    /**
-     * @see javax.net.ssl.SSLSessionContext#setSessionCacheSize(int size)
-     */
-    public void setSessionCacheSize(int size) throws IllegalArgumentException {
-        if (size < 0) {
-            throw new IllegalArgumentException("size < 0");
-        }
-        synchronized (sessions) {
-            cacheSize = size;
-            if (cacheSize > 0 && cacheSize < sessions.size()) {
-                int removals = sessions.size() - cacheSize;
-                Iterator<ByteBuffer> iterator = sessions.keySet().iterator();
-                while (removals-- > 0) {
-                    iterator.next();
-                    iterator.remove();
-                }
-                clone = (LinkedHashMap<ByteBuffer, SSLSession>)
-                        sessions.clone();
-            }
-        }
-    }
-
-    /**
-     * @see javax.net.ssl.SSLSessionContext#setSessionTimeout(int seconds)
-     */
-    public void setSessionTimeout(int seconds) throws IllegalArgumentException {
-        if (seconds < 0) {
-            throw new IllegalArgumentException("seconds < 0");
-        }
-        synchronized (sessions) {
-            timeout = seconds*1000;
-            // Check timeouts and remove expired sessions
-            SSLSession ses;
-            Iterator<Map.Entry<ByteBuffer, SSLSession>> iterator =
-                    sessions.entrySet().iterator();
-            while (iterator.hasNext()) {
-                SSLSession session = iterator.next().getValue();
-                if (!session.isValid()) {
-                    // safe to remove with this special method since it doesn't
-                    // make the iterator throw a ConcurrentModificationException
-                    iterator.remove();
-                }
-            }
-            clone = (LinkedHashMap<ByteBuffer, SSLSession>) sessions.clone();
-        }
-    }
-
-    /**
-     * Adds session to the session cache
-     * @param ses
-     */
-    void putSession(SSLSession ses) {
-        synchronized (sessions) {
-            sessions.put(ByteBuffer.wrap(ses.getId()), ses);
-            clone = (LinkedHashMap<ByteBuffer, SSLSession>) sessions.clone();
-        }
-    }
-}
diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSessionImpl.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSessionImpl.java
index df46094..4510c96 100644
--- a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSessionImpl.java
+++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSessionImpl.java
@@ -40,7 +40,6 @@
 import javax.net.ssl.SSLSessionContext;
 
 import org.apache.harmony.luni.util.TwoKeyHashMap;
-import org.apache.harmony.xnet.provider.jsse.SSLSessionContextImpl;
 
 /**
  * 
@@ -83,8 +82,9 @@
     /**
      * Context of the session
      */
-    SSLSessionContextImpl context;
-
+// BEGIN android-changed
+    SSLSessionContext context;
+// END android-changed
 
     /**
      * certificates were sent to the peer 
diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerHandshakeImpl.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerHandshakeImpl.java
index 93dc9c4..f6eef23 100644
--- a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerHandshakeImpl.java
+++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerHandshakeImpl.java
@@ -81,7 +81,6 @@
 
     /**
      * Start session negotiation
-     * @param session
      */
     public void start() {
         if (session == null) { // initial handshake
@@ -299,7 +298,7 @@
                     clientFinished = new Finished(io_stream, length);
                     verifyFinished(clientFinished.getData());
                     // BEGIN android-added
-                    session.context = parameters.getClientSessionContext();
+                    session.context = parameters.getServerSessionContext();
                     // END android-added
                     parameters.getServerSessionContext().putSession(session);
                     if (!isResuming) {
diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerSessionContext.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerSessionContext.java
new file mode 100644
index 0000000..a3c2c6d
--- /dev/null
+++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerSessionContext.java
@@ -0,0 +1,124 @@
+/*
+ * 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 org.apache.harmony.xnet.provider.jsse;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Iterator;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import javax.net.ssl.SSLSession;
+
+/**
+ * Caches server sessions. Indexes by session ID. Users typically look up
+ * sessions using the ID provided by an SSL client.
+ */
+public class ServerSessionContext extends AbstractSessionContext {
+
+    /*
+     * TODO: Expire timed-out sessions more pro-actively.
+     */
+
+    private final Map<ByteArray, SSLSession> sessions
+            = new LinkedHashMap<ByteArray, SSLSession>() {
+        @Override
+        protected boolean removeEldestEntry(
+                Map.Entry<ByteArray, SSLSession> eldest) {
+            return maximumSize > 0 && size() > maximumSize;
+        }
+    };
+
+    private final SSLServerSessionCache persistentCache;
+
+    public ServerSessionContext(SSLParameters parameters,
+            SSLServerSessionCache persistentCache) {
+        super(parameters, 100, 0);
+        this.persistentCache = persistentCache;
+    }
+
+    Iterator<SSLSession> sessionIterator() {
+        synchronized (sessions) {
+            SSLSession[] array = sessions.values().toArray(
+                    new SSLSession[sessions.size()]);
+            return Arrays.asList(array).iterator();
+        }
+    }
+
+    void trimToSize() {
+        synchronized (sessions) {
+            int size = sessions.size();
+            if (size > maximumSize) {
+                int removals = size - maximumSize;
+                Iterator<SSLSession> i = sessions.values().iterator();
+                do {
+                    i.next();
+                    i.remove();
+                } while (--removals > 0);
+            }
+        }
+    }
+
+    public void setSessionTimeout(int seconds)
+            throws IllegalArgumentException {
+        if (seconds < 0) {
+            throw new IllegalArgumentException("seconds < 0");
+        }
+        timeout = seconds;
+    }
+
+    public SSLSession getSession(byte[] sessionId) {
+        ByteArray key = new ByteArray(sessionId);
+        synchronized (sessions) {
+            SSLSession session = sessions.get(key);
+            if (session != null) {
+                return session;
+            }
+        }
+
+        // Check persistent cache.
+        if (persistentCache != null) {
+            byte[] data = persistentCache.getSessionData(sessionId);
+            if (data != null) {
+                SSLSession session = toSession(data, null, -1);
+                if (session != null) {
+                    synchronized (sessions) {
+                        sessions.put(key, session);
+                    }
+                    return session;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    void putSession(SSLSession session) {
+        ByteArray key = new ByteArray(session.getId());
+        synchronized (sessions) {
+            sessions.put(key, session);
+        }
+
+        // TODO: In background thread.
+        if (persistentCache != null) {
+            byte[] data = toBytes(session);
+            if (data != null) {
+                persistentCache.putSessionData(session, data);
+            }
+        }
+    }
+}
diff --git a/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl.cpp b/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl.cpp
index 0c0e455..feae690 100644
--- a/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl.cpp
+++ b/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl.cpp
@@ -61,7 +61,7 @@
 }
 
 /**
- * Gets the peer certificate in the chain and fills a byte array with the 
+ * Gets the peer certificate in the chain and fills a byte array with the
  * information therein.
  */
 static jobjectArray org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getpeercertificates(JNIEnv* env,
@@ -91,7 +91,62 @@
 }
 
 /**
- * Gets and returns in a byte array the ID of the actual SSL session. 
+ * Serialize the session.
+ * See apache mod_ssl
+ */
+static jbyteArray org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_serialize(JNIEnv* env, jobject object)
+{
+  SSL_SESSION * ssl_session;
+  jbyteArray bytes = NULL;
+  jbyte *tmp;
+  int size;
+  unsigned char *ucp;
+
+  ssl_session = getSslSessionPointer(env, object);
+  if (ssl_session == NULL) {
+    return NULL;
+  }
+
+  // Compute the size of the DER data
+  size = i2d_SSL_SESSION(ssl_session, NULL);
+  if (size == 0) {
+    return NULL;
+  }
+
+  bytes = env->NewByteArray(size);
+  if (bytes != NULL) {
+        tmp = env->GetByteArrayElements(bytes, NULL);
+        ucp = (unsigned char *) tmp;
+        i2d_SSL_SESSION(ssl_session, &ucp);
+        env->ReleaseByteArrayElements(bytes, tmp, 0);
+  }
+
+  return bytes;
+
+}
+
+/**
+ * Deserialize the session.
+ */
+static jint org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_deserialize(JNIEnv* env, jobject object, jbyteArray bytes, jint size)
+{
+  const unsigned char *ucp;
+  jbyte *tmp;
+  SSL_SESSION *ssl_session;
+
+  if (bytes != NULL) {
+        tmp = env->GetByteArrayElements(bytes, NULL);
+        ucp = (const unsigned char *) tmp;
+        ssl_session = d2i_SSL_SESSION(NULL, &ucp, (long) size);
+        env->ReleaseByteArrayElements(bytes, tmp, 0);
+
+        return (jint) ssl_session;
+  }
+  return 0;
+}
+
+/**
+ * Gets and returns in a byte array the ID of the actual SSL session.
  */
 static jbyteArray org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getid(JNIEnv* env, jobject object)
 {
@@ -112,8 +167,8 @@
 }
 
 /**
- * Gets and returns in a long integer the creation's time of the 
- * actual SSL session. 
+ * Gets and returns in a long integer the creation's time of the
+ * actual SSL session.
  */
 static jlong org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getcreationtime(JNIEnv* env, jobject object)
 {
@@ -126,7 +181,7 @@
 }
 
 /**
- * Gets and returns in a string the peer's host's name. 
+ * Gets and returns in a string the peer's host's name.
  */
 static jstring org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getpeerhost(JNIEnv* env, jobject object)
 {
@@ -158,7 +213,7 @@
 }
 
 /**
- * Gets and returns in a string the peer's port name (https, ftp, etc.). 
+ * Gets and returns in a string the peer's port name (https, ftp, etc.).
  */
 static jstring org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getpeerport(JNIEnv* env, jobject object)
 {
@@ -179,7 +234,7 @@
     bio = SSL_get_rbio(ssl);
     port = BIO_get_conn_port(bio);
 
-    /* Notice: port name can be NULL */    
+    /* Notice: port name can be NULL */
     result = env->NewStringUTF(port);
 
     SSL_free(ssl);
@@ -189,7 +244,7 @@
 }
 
 /**
- * Gets and returns in a string the version of the SSL protocol. If it 
+ * Gets and returns in a string the version of the SSL protocol. If it
  * returns the string "unknown" it means that no connection is established.
  */
 static jstring org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getprotocol(JNIEnv* env, jobject object)
@@ -218,7 +273,7 @@
 }
 
 /**
- * Gets and returns in a string the set of ciphers the actual SSL session uses. 
+ * Gets and returns in a string the set of ciphers the actual SSL session uses.
  */
 static jstring org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getciphersuite(JNIEnv* env, jobject object)
 {
@@ -264,7 +319,9 @@
     {"nativegetprotocol", "()Ljava/lang/String;", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getprotocol},
     {"nativegetciphersuite", "()Ljava/lang/String;", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getciphersuite},
     {"nativegetpeercertificates", "()[[B", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getpeercertificates},
-    {"nativefree", "(I)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_free}
+    {"nativefree", "(I)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_free},
+    {"nativeserialize", "()[B", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_serialize},
+    {"nativedeserialize", "([BI)I", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_deserialize}
 };
 
 /**
diff --git a/x-net/src/test/java/org/apache/harmony/xnet/provider/jsse/ClientSessionContextTest.java b/x-net/src/test/java/org/apache/harmony/xnet/provider/jsse/ClientSessionContextTest.java
new file mode 100644
index 0000000..af4490b
--- /dev/null
+++ b/x-net/src/test/java/org/apache/harmony/xnet/provider/jsse/ClientSessionContextTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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 org.apache.harmony.xnet.provider.jsse;
+
+import junit.framework.TestCase;
+
+import javax.net.ssl.SSLSession;
+import java.util.Enumeration;
+import java.util.Set;
+import java.util.HashSet;
+
+public class ClientSessionContextTest extends TestCase {
+
+    public void testGetSessionById() {
+        ClientSessionContext context = new ClientSessionContext(null, null);
+
+        SSLSession a = new FakeSession("a");
+        SSLSession b = new FakeSession("b");
+
+        context.putSession(a);
+        context.putSession(b);
+
+        assertSame(a, context.getSession("a".getBytes()));
+        assertSame(b, context.getSession("b".getBytes()));
+
+        assertSame(a, context.getSession("a", 443));
+        assertSame(b, context.getSession("b", 443));
+
+        assertEquals(2, context.sessions.size());
+
+        Set<SSLSession> sessions = new HashSet<SSLSession>();
+        Enumeration ids = context.getIds();
+        while (ids.hasMoreElements()) {
+            sessions.add(context.getSession((byte[]) ids.nextElement()));
+        }
+
+        Set<SSLSession> expected = new HashSet<SSLSession>();
+        expected.add(a);
+        expected.add(b);
+
+        assertEquals(expected, sessions);
+    }
+
+    public void testTrimToSize() {
+        ClientSessionContext context = new ClientSessionContext(null, null);
+
+        FakeSession a = new FakeSession("a");
+        FakeSession b = new FakeSession("b");
+        FakeSession c = new FakeSession("c");
+        FakeSession d = new FakeSession("d");
+
+        context.putSession(a);
+        context.putSession(b);
+        context.putSession(c);
+        context.putSession(d);
+
+        context.setSessionCacheSize(2);
+
+        Set<SSLSession> sessions = new HashSet<SSLSession>();
+        Enumeration ids = context.getIds();
+        while (ids.hasMoreElements()) {
+            sessions.add(context.getSession((byte[]) ids.nextElement()));
+        }
+
+        Set<SSLSession> expected = new HashSet<SSLSession>();
+        expected.add(c);
+        expected.add(d);
+
+        assertEquals(expected, sessions);               
+    }
+
+}
diff --git a/x-net/src/test/java/org/apache/harmony/xnet/provider/jsse/FakeSession.java b/x-net/src/test/java/org/apache/harmony/xnet/provider/jsse/FakeSession.java
new file mode 100644
index 0000000..4a793dd
--- /dev/null
+++ b/x-net/src/test/java/org/apache/harmony/xnet/provider/jsse/FakeSession.java
@@ -0,0 +1,114 @@
+/*
+ * 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 org.apache.harmony.xnet.provider.jsse;
+
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSessionContext;
+import java.security.cert.Certificate;
+import java.security.Principal;
+
+class FakeSession implements SSLSession {
+    final String host;
+
+    FakeSession(String host) {
+        this.host = host;
+    }
+
+    public int getApplicationBufferSize() {
+        throw new UnsupportedOperationException();
+    }
+
+    public String getCipherSuite() {
+        throw new UnsupportedOperationException();
+    }
+
+    public long getCreationTime() {
+        throw new UnsupportedOperationException();
+    }
+
+    public byte[] getId() {
+        return host.getBytes();
+    }
+
+    public long getLastAccessedTime() {
+        throw new UnsupportedOperationException();
+    }
+
+    public Certificate[] getLocalCertificates() {
+        throw new UnsupportedOperationException();
+    }
+
+    public Principal getLocalPrincipal() {
+        throw new UnsupportedOperationException();
+    }
+
+    public int getPacketBufferSize() {
+        throw new UnsupportedOperationException();
+    }
+
+    public javax.security.cert.X509Certificate[] getPeerCertificateChain() {
+        throw new UnsupportedOperationException();
+    }
+
+    public Certificate[] getPeerCertificates() {
+        throw new UnsupportedOperationException();
+    }
+
+    public String getPeerHost() {
+        return host;
+    }
+
+    public int getPeerPort() {
+        return 443;
+    }
+
+    public Principal getPeerPrincipal() {
+        throw new UnsupportedOperationException();
+    }
+
+    public String getProtocol() {
+        throw new UnsupportedOperationException();
+    }
+
+    public SSLSessionContext getSessionContext() {
+        throw new UnsupportedOperationException();
+    }
+
+    public Object getValue(String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    public String[] getValueNames() {
+        throw new UnsupportedOperationException();
+    }
+
+    public void invalidate() {
+        throw new UnsupportedOperationException();
+    }
+
+    public boolean isValid() {
+        throw new UnsupportedOperationException();
+    }
+
+    public void putValue(String name, Object value) {
+        throw new UnsupportedOperationException();
+    }
+
+    public void removeValue(String name) {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/x-net/src/test/java/org/apache/harmony/xnet/provider/jsse/FileClientSessionCacheTest.java b/x-net/src/test/java/org/apache/harmony/xnet/provider/jsse/FileClientSessionCacheTest.java
new file mode 100644
index 0000000..ee50863a
--- /dev/null
+++ b/x-net/src/test/java/org/apache/harmony/xnet/provider/jsse/FileClientSessionCacheTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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 org.apache.harmony.xnet.provider.jsse;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.IOException;
+
+public class FileClientSessionCacheTest extends TestCase {
+
+    public void testMaxSize() throws IOException, InterruptedException {
+        String tmpDir = System.getProperty("java.io.tmpdir");
+        if (tmpDir == null) {
+            fail("Please set 'java.io.tmpdir' system property.");
+        }
+        File cacheDir = new File(tmpDir
+                + "/" + FileClientSessionCacheTest.class.getName() + "/cache");
+        final SSLClientSessionCache cache
+                = FileClientSessionCache.usingDirectory(cacheDir);
+        Thread[] threads = new Thread[10];
+        final int iterations = FileClientSessionCache.MAX_SIZE * 10;
+        for (int i = 0; i < threads.length; i++) {
+            final int id = i;
+            threads[i] = new Thread() {
+                @Override
+                public void run() {
+                    for (int i = 0; i < iterations; i++) {
+                        cache.putSessionData(new FakeSession(id + "." + i),
+                                new byte[10]);
+                    }
+                }
+            };
+        }
+        for (int i = 0; i < threads.length; i++) {
+            threads[i].start();
+        }
+        for (int i = 0; i < threads.length; i++) {
+            threads[i].join();
+        }
+        assertEquals(FileClientSessionCache.MAX_SIZE, cacheDir.list().length);
+    }
+}