Merge "Expose getCurrentSpi from crypto operations as hidden API."
diff --git a/JavaLibrary.mk b/JavaLibrary.mk
index 9f6d827..fb0b207 100644
--- a/JavaLibrary.mk
+++ b/JavaLibrary.mk
@@ -116,16 +116,6 @@
 LOCAL_JARJAR_RULES := $(LOCAL_PATH)/jarjar-rules.txt
 include $(BUILD_JAVA_LIBRARY)
 
-# Path to the ICU4C data files in the Android device file system:
-icu4c_data := /system/usr/icu
-# TODO: It's quite hideous that this double-slash between icu4j and main is required.
-# It's because we provide a variable substition of the make-rule generated jar command
-# to substitute a processed ICUProperties.config file in place of the original.
-#
-# We can avoid this by filtering out ICUConfig.properties from our list of resources.
-icu4j_config_root := $(LOCAL_PATH)/../external/icu/icu4j//main/classes/core/src
-include external/icu/icu4j/adjust_icudt_path.mk
-
 ifeq ($(LIBCORE_SKIP_TESTS),)
 # Make the core-tests library.
 include $(CLEAR_VARS)
diff --git a/benchmarks/src/benchmarks/regression/ProviderBenchmark.java b/benchmarks/src/benchmarks/regression/ProviderBenchmark.java
new file mode 100644
index 0000000..649aa01
--- /dev/null
+++ b/benchmarks/src/benchmarks/regression/ProviderBenchmark.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2015 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 benchmarks.regression;
+
+import java.security.Provider;
+import java.security.Security;
+import javax.crypto.Cipher;
+
+import com.google.caliper.Param;
+import com.google.caliper.SimpleBenchmark;
+import javax.net.ssl.SSLSocketFactory;
+
+public class ProviderBenchmark extends SimpleBenchmark {
+    public void timeStableProviders(int reps) throws Exception {
+        for (int i = 0; i < reps; ++i) {
+            Cipher c = Cipher.getInstance("RSA");
+        }
+    }
+
+    public void timeWithNewProvider(int reps) throws Exception {
+        for (int i = 0; i < reps; ++i) {
+            Security.addProvider(new MockProvider());
+            try {
+                Cipher c = Cipher.getInstance("RSA");
+            } finally {
+                Security.removeProvider("Mock");
+            }
+        }
+    }
+
+    private static class MockProvider extends Provider {
+        public MockProvider() {
+            super("Mock", 1.0, "Mock me!");
+        }
+    }
+}
diff --git a/expectations/knownfailures.txt b/expectations/knownfailures.txt
index a3e9197..6aac9eb 100644
--- a/expectations/knownfailures.txt
+++ b/expectations/knownfailures.txt
@@ -1511,11 +1511,11 @@
   ]
 },
 {
-  description: "ICU bug in formatting of dates that span month boundaries",
+  description: "OsTest.test_PacketSocketAddress needs CAP_NET_RAW",
+  bug: 19764047,
   result: EXEC_FAILED,
-  bug: 20708022,
   names: [
-    "libcore.icu.DateIntervalFormatTest#testEndOfDayOnLastDayOfMonth"
+    "libcore.io.OsTest#test_PacketSocketAddress"
   ]
 }
 ]
diff --git a/luni/src/main/java/java/security/Provider.java b/luni/src/main/java/java/security/Provider.java
index 1704b58..1a64ecc 100644
--- a/luni/src/main/java/java/security/Provider.java
+++ b/luni/src/main/java/java/security/Provider.java
@@ -368,8 +368,8 @@
     }
 
     /**
-     * Get the service of the specified type
-     *
+     * Get the service of the specified {@code type} (e.g. "SecureRandom",
+     * "Signature").
      */
     synchronized Provider.Service getService(String type) {
         updatePropertyServiceTable();
diff --git a/luni/src/main/java/java/util/ComparableTimSort.java b/luni/src/main/java/java/util/ComparableTimSort.java
index aba7573..f3da001 100644
--- a/luni/src/main/java/java/util/ComparableTimSort.java
+++ b/luni/src/main/java/java/util/ComparableTimSort.java
@@ -94,7 +94,7 @@
     private final int[] runLen;
 
     /**
-     * Asserts have been placed in if-statements for performace. To enable them,
+     * Asserts have been placed in if-statements for performance. To enable them,
      * set this field to true and enable them in VM with a command line flag.
      * If you modify this class, please do test the asserts!
      */
@@ -363,11 +363,28 @@
      */
     private void mergeCollapse() {
         while (stackSize > 1) {
-            int n = stackSize - 2;
+            final int n = stackSize - 2;
             if (n > 0 && runLen[n-1] <= runLen[n] + runLen[n+1]) {
-                if (runLen[n - 1] < runLen[n + 1])
-                    n--;
-                mergeAt(n);
+                // Merge the smaller of runLen[n-1] or runLen[n + 1] with runLen[n].
+                if (runLen[n - 1] < runLen[n + 1]) {
+                    // runLen[n-1] is smallest. Merge runLen[n] into runLen[n - 1], leaving
+                    // runLen[n+1] as the new runLen[n].
+                    mergeAt(n - 1);
+                    // n is now stackSize - 1, the top of the stack.
+                    // Fix for http://b/19493779
+                    // Because we modified runLen[n - 1] we might have affected invariant 1 as far
+                    // back as runLen[n - 3]. Check we did not violate it.
+                    if (n > 2 && runLen[n-3] <= runLen[n-2] + runLen[n-1]) {
+                        // Avoid leaving invariant 1 still violated on the next loop by also merging
+                        // runLen[n] into runLen[n - 1].
+                        mergeAt(n - 1);
+                        // Now the last three elements in the stack will again be the only elements
+                        // that might break the invariant and we can loop again safely.
+                    }
+                } else {
+                    // runLen[n+1] is smallest. Merge runLen[n + 1] into runLen[n].
+                    mergeAt(n);
+                }
             } else if (runLen[n] <= runLen[n + 1]) {
                 mergeAt(n);
             } else {
diff --git a/luni/src/main/java/java/util/List.java b/luni/src/main/java/java/util/List.java
index 8a9e1e3..54c8b65 100644
--- a/luni/src/main/java/java/util/List.java
+++ b/luni/src/main/java/java/util/List.java
@@ -84,7 +84,8 @@
      * @throws IllegalArgumentException
      *                if an object cannot be added to this {@code List}.
      * @throws IndexOutOfBoundsException
-     *                if {@code location < 0 || location > size()}
+     *                if {@code location < 0 || location > size()}.
+     * @throws NullPointerException if {@code collection} is {@code null}.
      */
     public boolean addAll(int location, Collection<? extends E> collection);
 
@@ -104,6 +105,7 @@
      *                {@code List}.
      * @throws IllegalArgumentException
      *                if an object cannot be added to this {@code List}.
+     * @throws NullPointerException if {@code collection} is {@code null}.
      */
     public boolean addAll(Collection<? extends E> collection);
 
@@ -135,6 +137,7 @@
      *            the collection of objects
      * @return {@code true} if all objects in the specified collection are
      *         elements of this {@code List}, {@code false} otherwise.
+     * @throws NullPointerException if {@code collection} is {@code null}.
      */
     public boolean containsAll(Collection<?> collection);
 
@@ -269,6 +272,7 @@
      * @return {@code true} if this {@code List} is modified, {@code false} otherwise.
      * @throws UnsupportedOperationException
      *                if removing from this {@code List} is not supported.
+     * @throws NullPointerException if {@code collection} is {@code null}.
      */
     public boolean removeAll(Collection<?> collection);
 
@@ -281,6 +285,7 @@
      * @return {@code true} if this {@code List} is modified, {@code false} otherwise.
      * @throws UnsupportedOperationException
      *                if removing from this {@code List} is not supported.
+     * @throws NullPointerException if {@code collection} is {@code null}.
      */
     public boolean retainAll(Collection<?> collection);
 
diff --git a/luni/src/main/java/java/util/Locale.java b/luni/src/main/java/java/util/Locale.java
index 1885f83..7226e1c 100644
--- a/luni/src/main/java/java/util/Locale.java
+++ b/luni/src/main/java/java/util/Locale.java
@@ -744,12 +744,13 @@
 
             final String normalizedValue = value.toLowerCase(Locale.ROOT).replace('_', '-');
             final String[] subtags = normalizedValue.split("-");
+            final char normalizedKey = Character.toLowerCase(key);
 
             // Lengths for subtags in the private use extension should be [1, 8] chars.
             // For all other extensions, they should be [2, 8] chars.
             //
             // http://www.rfc-editor.org/rfc/bcp/bcp47.txt
-            final int minimumLength = (key == PRIVATE_USE_EXTENSION) ? 1 : 2;
+            final int minimumLength = (normalizedKey == PRIVATE_USE_EXTENSION) ? 1 : 2;
             for (String subtag : subtags) {
                 if (!isValidBcp47Alphanum(subtag, minimumLength, 8)) {
                     throw new IllformedLocaleException(
@@ -759,14 +760,14 @@
 
             // We need to take special action in the case of unicode extensions,
             // since we claim to understand their keywords and attributes.
-            if (key == UNICODE_LOCALE_EXTENSION) {
+            if (normalizedKey == UNICODE_LOCALE_EXTENSION) {
                 // First clear existing attributes and keywords.
                 extensions.clear();
                 attributes.clear();
 
                 parseUnicodeExtension(subtags, keywords, attributes);
             } else {
-                extensions.put(key, normalizedValue);
+                extensions.put(normalizedKey, normalizedValue);
             }
 
             return this;
@@ -986,6 +987,27 @@
             this.unicodeKeywords = Collections.unmodifiableMap(keywordsCopy);
             this.extensions = Collections.unmodifiableMap(extensionsCopy);
         } else {
+
+            // The locales ja_JP_JP and th_TH_TH are ill formed since their variant is too
+            // short, however they have been used to represent a locale with the japanese imperial
+            // calendar and thai numbering respectively. We add an extension in their constructor
+            // to modernize them.
+            if ("ja".equals(language) && "JP".equals(country) && "JP".equals(variant)) {
+                Map<String, String> keywordsCopy = new TreeMap<>(unicodeKeywords);
+                keywordsCopy.put("ca", "japanese");
+                unicodeKeywords = keywordsCopy;
+            } else if ("th".equals(language) && "TH".equals(country) && "TH".equals(variant)) {
+                Map<String, String> keywordsCopy = new TreeMap<>(unicodeKeywords);
+                keywordsCopy.put("nu", "thai");
+                unicodeKeywords = keywordsCopy;
+            }
+
+            if (!unicodeKeywords.isEmpty() || !unicodeAttributes.isEmpty()) {
+                Map<Character, String> extensionsCopy = new TreeMap<>(extensions);
+                addUnicodeExtensionToExtensionsMap(unicodeAttributes, unicodeKeywords, extensionsCopy);
+                extensions = extensionsCopy;
+            }
+
             this.unicodeAttributes = unicodeAttributes;
             this.unicodeKeywords = unicodeKeywords;
             this.extensions = extensions;
@@ -2134,7 +2156,7 @@
                         return extensionKeyIndex;
                     }
 
-                    final String key = subtags[extensionKeyIndex];
+                    final String key = subtags[extensionKeyIndex].toLowerCase(Locale.ROOT);
                     if (extensions.containsKey(key.charAt(0))) {
                         return extensionKeyIndex;
                     }
@@ -2146,7 +2168,7 @@
                 // Mark the start of the next extension. Also keep track of whether this
                 // is a private use extension, and throw an error if it doesn't come last.
                 extensionKeyIndex = i;
-                if ("x".equals(subtag)) {
+                if ("x".equals(subtag.toLowerCase(Locale.ROOT))) {
                     privateUseExtensionIndex = i;
                 } else if (privateUseExtensionIndex != -1) {
                     // The private use extension must come last.
@@ -2169,7 +2191,7 @@
                 return extensionKeyIndex;
             }
 
-            final String key = subtags[extensionKeyIndex];
+            final String key = subtags[extensionKeyIndex].toLowerCase(Locale.ROOT);
             if (extensions.containsKey(key.charAt(0))) {
                 return extensionKeyIndex;
             }
diff --git a/luni/src/main/java/java/util/TimSort.java b/luni/src/main/java/java/util/TimSort.java
index ffe4e17..1a566c2 100644
--- a/luni/src/main/java/java/util/TimSort.java
+++ b/luni/src/main/java/java/util/TimSort.java
@@ -119,7 +119,7 @@
     private final int[] runLen;
 
     /**
-     * Asserts have been placed in if-statements for performace. To enable them,
+     * Asserts have been placed in if-statements for performance. To enable them,
      * set this field to true and enable them in VM with a command line flag.
      * If you modify this class, please do test the asserts!
      */
@@ -397,11 +397,29 @@
      */
     private void mergeCollapse() {
         while (stackSize > 1) {
-            int n = stackSize - 2;
+            final int n = stackSize - 2;
             if (n > 0 && runLen[n-1] <= runLen[n] + runLen[n+1]) {
-                if (runLen[n - 1] < runLen[n + 1])
-                    n--;
-                mergeAt(n);
+                // Merge the smaller of runLen[n-1] or runLen[n + 1] with runLen[n].
+                if (runLen[n - 1] < runLen[n + 1]) {
+                    // runLen[n-1] is smallest. Merge runLen[n] into runLen[n - 1], leaving
+                    // runLen[n+1] as the new runLen[n].
+                    mergeAt(n - 1);
+                    // n is now stackSize - 1, the top of the stack.
+
+                    // Fix for http://b/19493779
+                    // Because we modified runLen[n - 1] we might have affected invariant 1 as far
+                    // back as runLen[n - 3]. Check we did not violate it.
+                    if (n > 2 && runLen[n-3] <= runLen[n-2] + runLen[n-1]) {
+                        // Avoid leaving invariant 1 still violated on the next loop by also merging
+                        // runLen[n] into runLen[n - 1].
+                        mergeAt(n - 1);
+                        // Now the last three elements in the stack will again be the only elements
+                        // that might break the invariant and we can loop again safely.
+                    }
+                } else {
+                    // runLen[n+1] is smallest. Merge runLen[n + 1] into runLen[n].
+                    mergeAt(n);
+                }
             } else if (runLen[n] <= runLen[n + 1]) {
                 mergeAt(n);
             } else {
diff --git a/luni/src/main/java/libcore/icu/DateIntervalFormat.java b/luni/src/main/java/libcore/icu/DateIntervalFormat.java
index 509d0a0..7e7ad01 100644
--- a/luni/src/main/java/libcore/icu/DateIntervalFormat.java
+++ b/luni/src/main/java/libcore/icu/DateIntervalFormat.java
@@ -75,7 +75,7 @@
     if (startMs != endMs && endsAtMidnight &&
         ((flags & DateUtilsBridge.FORMAT_SHOW_TIME) == 0
             || DateUtilsBridge.dayDistance(startCalendar, endCalendar) <= 1)) {
-      endCalendar.roll(Calendar.DAY_OF_MONTH, false);
+      endCalendar.add(Calendar.DAY_OF_MONTH, -1);
     }
 
     String skeleton = DateUtilsBridge.toSkeleton(startCalendar, endCalendar, flags);
diff --git a/luni/src/main/java/org/apache/harmony/security/fortress/Engine.java b/luni/src/main/java/org/apache/harmony/security/fortress/Engine.java
index 855a8c7..1c794e5 100644
--- a/luni/src/main/java/org/apache/harmony/security/fortress/Engine.java
+++ b/luni/src/main/java/org/apache/harmony/security/fortress/Engine.java
@@ -152,7 +152,8 @@
     }
 
     /**
-     * Returns a list of all possible matches for a given algorithm.
+     * Returns a list of all possible matches for a given algorithm. Returns
+     * {@code null} if no matches were found.
      */
     public ArrayList<Provider.Service> getServices(String algorithm) {
         int newCacheVersion = Services.getCacheVersion();
@@ -163,8 +164,7 @@
                 && newCacheVersion == cacheEntry.cacheVersion) {
             return cacheEntry.services;
         }
-        String name = this.serviceName + "." + algoUC;
-        ArrayList<Provider.Service> services = Services.getServices(name);
+        ArrayList<Provider.Service> services = Services.getServices(serviceName, algoUC);
         this.serviceCache = new ServiceCacheEntry(algoUC, newCacheVersion, services);
         return services;
     }
diff --git a/luni/src/main/java/org/apache/harmony/security/fortress/Services.java b/luni/src/main/java/org/apache/harmony/security/fortress/Services.java
index c81bf6b..5f3dfe0 100644
--- a/luni/src/main/java/org/apache/harmony/security/fortress/Services.java
+++ b/luni/src/main/java/org/apache/harmony/security/fortress/Services.java
@@ -29,16 +29,6 @@
  * implementations for all "serviceName.algName".
  */
 public class Services {
-
-    /**
-     * The HashMap that contains information about preferred implementations for
-     * all serviceName.algName in the registered providers.
-     * Set the initial size to 600 so we don't grow to 1024 by default because
-     * initialization adds a few entries more than the growth threshold.
-     */
-    private static final HashMap<String, ArrayList<Provider.Service>> services
-            = new HashMap<String, ArrayList<Provider.Service>>(600);
-
     /**
      * Save default SecureRandom service as well.
      * Avoids similar provider/services iteration in SecureRandom constructor.
@@ -71,7 +61,6 @@
             Provider p = (Provider) providerClass.newInstance();
             providers.add(p);
             providersNames.put(p.getName(), p);
-            initServiceInfo(p);
             return true;
         } catch (ClassNotFoundException ignored) {
         } catch (IllegalAccessException ignored) {
@@ -104,7 +93,7 @@
     }
 
     /**
-     * Returns a copy of the registered providers as an array.
+     * Returns the actual registered providers.
      */
     public static synchronized ArrayList<Provider> getProviders() {
         return providers;
@@ -144,54 +133,39 @@
     }
 
     /**
-     * Adds information about provider services into HashMap.
+     * Looks up the requested service by type and algorithm. The service
+     * {@code type} and should be provided in the same format used when
+     * registering a service with a provider, for example, "KeyFactory.RSA".
+     * Callers can cache the returned service information but such caches should
+     * be validated against the result of Service.getCacheVersion() before use.
+     * Returns {@code null} if there are no services found.
      */
-    public static synchronized void initServiceInfo(Provider p) {
-        for (Provider.Service service : p.getServices()) {
-            String type = service.getType();
-            if (cachedSecureRandomService == null && type.equals("SecureRandom")) {
-                cachedSecureRandomService = service;
-            }
-            String key = type + "." + service.getAlgorithm().toUpperCase(Locale.US);
-            appendServiceLocked(key, service);
-            for (String alias : Engine.door.getAliases(service)) {
-                key = type + "." + alias.toUpperCase(Locale.US);
-                appendServiceLocked(key, service);
+    public static synchronized ArrayList<Provider.Service> getServices(String type,
+            String algorithm) {
+        ArrayList<Provider.Service> services = null;
+        for (Provider p : providers) {
+            Provider.Service s = p.getService(type, algorithm);
+            if (s != null) {
+                if (services == null) {
+                    services = new ArrayList<>(providers.size());
+                }
+                services.add(s);
             }
         }
+        return services;
     }
 
     /**
-     * Add or append the service to the key.
+     * Finds the first service offered of {@code type} and returns it.
      */
-    private static void appendServiceLocked(String key, Provider.Service service) {
-        ArrayList<Provider.Service> serviceList = services.get(key);
-        if (serviceList == null) {
-            serviceList = new ArrayList<Provider.Service>(1);
-            services.put(key, serviceList);
+    private static synchronized Provider.Service getFirstServiceOfType(String type) {
+        for (Provider p : providers) {
+            Provider.Service s = Engine.door.getService(p, type);
+            if (s != null) {
+                return s;
+            }
         }
-        serviceList.add(service);
-    }
-
-    /**
-     * Returns true if services does not contain any provider information.
-     */
-    public static synchronized boolean isEmpty() {
-        return services.isEmpty();
-    }
-
-    /**
-     * Looks up the requested service by type and algorithm. The
-     * service key should be provided in the same format used when
-     * registering a service with a provider, for example,
-     * "KeyFactory.RSA".
-     *
-     * Callers can cache the returned service information but such
-     * caches should be validated against the result of
-     * Service.getCacheVersion() before use.
-     */
-    public static synchronized ArrayList<Provider.Service> getServices(String key) {
-        return services.get(key);
+        return null;
     }
 
     /**
@@ -219,13 +193,7 @@
     public static synchronized int getCacheVersion() {
         if (needRefresh) {
             cacheVersion++;
-            synchronized (services) {
-                services.clear();
-            }
-            cachedSecureRandomService = null;
-            for (Provider p : providers) {
-                initServiceInfo(p);
-            }
+            cachedSecureRandomService = getFirstServiceOfType("SecureRandom");
             needRefresh = false;
         }
         return cacheVersion;
diff --git a/luni/src/test/java/libcore/icu/DateIntervalFormatTest.java b/luni/src/test/java/libcore/icu/DateIntervalFormatTest.java
index 6e0bce6..4c22371 100644
--- a/luni/src/test/java/libcore/icu/DateIntervalFormatTest.java
+++ b/luni/src/test/java/libcore/icu/DateIntervalFormatTest.java
@@ -429,7 +429,7 @@
     final ULocale locale = new ULocale("en");
     final TimeZone timeZone = TimeZone.getTimeZone("UTC");
 
-    assertEquals("April 30, 11:00 PM – May 1, 12:00 AM", formatDateRange(locale, timeZone,
+    assertEquals("11:00 PM – 12:00 AM", formatDateRange(locale, timeZone,
             1430434800000L, 1430438400000L, FORMAT_SHOW_TIME));
   }
 }
diff --git a/luni/src/test/java/libcore/icu/ICUTest.java b/luni/src/test/java/libcore/icu/ICUTest.java
index 99679a7..5eab070 100644
--- a/luni/src/test/java/libcore/icu/ICUTest.java
+++ b/luni/src/test/java/libcore/icu/ICUTest.java
@@ -260,5 +260,8 @@
     String icu4cTzVersion = ICU.getTZDataVersion();
     String zoneInfoTzVersion = ZoneInfoDB.getInstance().getVersion();
     assertEquals(icu4cTzVersion, zoneInfoTzVersion);
+
+    String icu4jTzVersion = android.icu.util.TimeZone.getTZDataVersion();
+    assertEquals(icu4jTzVersion, zoneInfoTzVersion);
   }
 }
diff --git a/luni/src/test/java/libcore/java/security/KeyPairGeneratorTest.java b/luni/src/test/java/libcore/java/security/KeyPairGeneratorTest.java
index 7e08b5f..1320a8a 100644
--- a/luni/src/test/java/libcore/java/security/KeyPairGeneratorTest.java
+++ b/luni/src/test/java/libcore/java/security/KeyPairGeneratorTest.java
@@ -45,6 +45,7 @@
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import javax.crypto.interfaces.DHPrivateKey;
@@ -198,7 +199,6 @@
         putKeySize("DiffieHellman", 512);
         putKeySize("DiffieHellman", 512+64);
         putKeySize("DiffieHellman", 1024);
-        putKeySize("EC", 192);
         putKeySize("EC", 224);
         putKeySize("EC", 256);
         putKeySize("EC", 384);
@@ -207,10 +207,10 @@
 
     /** Elliptic Curve Crypto named curves that should be supported. */
     private static final String[] EC_NAMED_CURVES = {
-        // NIST P-192 aka SECG secp192r1 aka ANSI X9.62 prime192v1
-        "secp192r1", "prime192v1",
         // NIST P-256 aka SECG secp256r1 aka ANSI X9.62 prime256v1
         "secp256r1", "prime256v1",
+        // NIST P-521 aka SECG secp521r1
+        "secp521r1",
     };
 
     private void test_KeyPairGenerator(KeyPairGenerator kpg) throws Exception {
@@ -267,7 +267,7 @@
     }
 
     private void test_Key(KeyPairGenerator kpg, Key k) throws Exception {
-        String expectedAlgorithm = kpg.getAlgorithm().toUpperCase();
+        String expectedAlgorithm = kpg.getAlgorithm().toUpperCase(Locale.ROOT);
         if (StandardNames.IS_RI && expectedAlgorithm.equals("DIFFIEHELLMAN")) {
             expectedAlgorithm = "DH";
         }
diff --git a/luni/src/test/java/libcore/java/security/ProviderTest.java b/luni/src/test/java/libcore/java/security/ProviderTest.java
index 0be558e..f234122 100644
--- a/luni/src/test/java/libcore/java/security/ProviderTest.java
+++ b/luni/src/test/java/libcore/java/security/ProviderTest.java
@@ -547,6 +547,15 @@
         }
     }
 
+    public void testProvider_removeProvider_Success() throws Exception {
+        MockProvider provider = new MockProvider("MockProvider");
+        assertNotNull(Security.getProvider(provider.getName()));
+        Security.addProvider(provider);
+        assertNotNull(Security.getProvider(provider.getName()));
+        Security.removeProvider(provider.getName());
+        assertNull(Security.getProvider(provider.getName()));
+    }
+
     public static class MyCertStoreSpi extends CertStoreSpi {
         public MyCertStoreSpi(CertStoreParameters params) throws InvalidAlgorithmParameterException {
             super(params);
diff --git a/luni/src/test/java/libcore/java/security/SignatureTest.java b/luni/src/test/java/libcore/java/security/SignatureTest.java
index e546f4f..f28446c 100644
--- a/luni/src/test/java/libcore/java/security/SignatureTest.java
+++ b/luni/src/test/java/libcore/java/security/SignatureTest.java
@@ -31,6 +31,11 @@
 import java.security.SignatureException;
 import java.security.spec.DSAPrivateKeySpec;
 import java.security.spec.DSAPublicKeySpec;
+import java.security.spec.ECFieldFp;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+import java.security.spec.ECPublicKeySpec;
+import java.security.spec.EllipticCurve;
 import java.security.spec.InvalidKeySpecException;
 import java.security.spec.RSAPrivateCrtKeySpec;
 import java.security.spec.RSAPrivateKeySpec;
@@ -1664,4 +1669,43 @@
         es.shutdown();
         assertTrue("Test should not timeout", es.awaitTermination(1, TimeUnit.MINUTES));
     }
+
+    public void testArbitraryCurve() throws Exception {
+        // These are the parameters for the BitCoin curve (secp256k1). See
+        // https://en.bitcoin.it/wiki/Secp256k1.
+        final BigInteger p = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16);
+        final BigInteger a = BigInteger.valueOf(0);
+        final BigInteger b = BigInteger.valueOf(7);
+        final BigInteger x = new BigInteger("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16);
+        final BigInteger y = new BigInteger("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 16);
+        final BigInteger order = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16);
+        final int cofactor = 1;
+
+        final ECParameterSpec spec = new ECParameterSpec(new EllipticCurve(new ECFieldFp(p), a, b), new ECPoint(x, y), order, cofactor);
+        final KeyFactory factory = KeyFactory.getInstance("EC");
+
+        // $ openssl ecparam -name secp256k1 -genkey > key.pem
+        // $ openssl ec -text -noout < key.pem
+        final BigInteger Px = new BigInteger("2d45572747a625db5fd23b30f97044a682f2d42d31959295043c1fa0034c8ed3", 16);
+        final BigInteger Py = new BigInteger("4d330f52e4bba00145a331041c8bbcf300c4fbfdf3d63d8de7608155b2793808", 16);
+
+        final ECPublicKeySpec keySpec = new ECPublicKeySpec(new ECPoint(Px, Py), spec);
+        final PublicKey pub = factory.generatePublic(keySpec);
+
+        // $ echo -n "Satoshi Nakamoto" > signed
+        // $ openssl dgst -ecdsa-with-SHA1 -sign key.pem -out sig signed
+        final byte[] SIGNATURE = hexToBytes("304402205b41ece6dcc1c5bfcfdae74658d99c08c5e783f3926c11ecc1a8bea5d95cdf27022061a7d5fc687287e2e02dd7c6723e2e27fe0555f789590a37e96b1bb0355b4df0");
+
+        Signature ecdsaVerify = Signature.getInstance("SHA1withECDSA");
+        ecdsaVerify.initVerify(pub);
+        ecdsaVerify.update("Satoshi Nakamoto".getBytes("UTF-8"));
+        boolean result = ecdsaVerify.verify(SIGNATURE);
+        assertEquals(true, result);
+
+        ecdsaVerify = Signature.getInstance("SHA1withECDSA");
+        ecdsaVerify.initVerify(pub);
+        ecdsaVerify.update("Not Satoshi Nakamoto".getBytes("UTF-8"));
+        result = ecdsaVerify.verify(SIGNATURE);
+        assertEquals(false, result);
+    }
 }
diff --git a/luni/src/test/java/libcore/java/util/LocaleTest.java b/luni/src/test/java/libcore/java/util/LocaleTest.java
index e1e84ab..9005f25 100644
--- a/luni/src/test/java/libcore/java/util/LocaleTest.java
+++ b/luni/src/test/java/libcore/java/util/LocaleTest.java
@@ -1203,4 +1203,38 @@
             System.setUnchangeableSystemProperty("user.locale", userLocale);
         }
     }
+
+    // http://b/20252611
+    public void testLegacyLocalesWithExtensions() {
+        Locale ja_JP_JP = new Locale("ja", "JP", "JP");
+        assertEquals("ca-japanese", ja_JP_JP.getExtension(Locale.UNICODE_LOCALE_EXTENSION));
+        assertEquals("japanese", ja_JP_JP.getUnicodeLocaleType("ca"));
+
+        Locale th_TH_TH = new Locale("th", "TH", "TH");
+        assertEquals("nu-thai", th_TH_TH.getExtension(Locale.UNICODE_LOCALE_EXTENSION));
+        assertEquals("thai", th_TH_TH.getUnicodeLocaleType("nu"));
+    }
+
+    // http://b/20252611
+    public void testLowerCaseExtensionKeys() {
+        // We must lowercase extension keys in forLanguageTag..
+        Locale ar_EG = Locale.forLanguageTag("ar-EG-U-nu-arab");
+        assertEquals("nu-arab", ar_EG.getExtension(Locale.UNICODE_LOCALE_EXTENSION));
+        assertEquals("ar-EG-u-nu-arab", ar_EG.toLanguageTag());
+
+        // ... and in builders.
+        Locale.Builder b = new Locale.Builder();
+        b.setLanguage("ar");
+        b.setRegion("EG");
+        b.setExtension('U', "nu-arab");
+        assertEquals("ar-EG-u-nu-arab", b.build().toLanguageTag());
+
+        // Corollary : extension keys are case insensitive.
+        b = new Locale.Builder();
+        b.setLanguage("ar");
+        b.setRegion("EG");
+        b.setExtension('U', "nu-arab");
+        b.setExtension('u', "nu-thai");
+        assertEquals("ar-EG-u-nu-thai", b.build().toLanguageTag());
+    }
 }
diff --git a/luni/src/test/java/libcore/java/util/TimSortTest.java b/luni/src/test/java/libcore/java/util/TimSortTest.java
new file mode 100644
index 0000000..0e928ba
--- /dev/null
+++ b/luni/src/test/java/libcore/java/util/TimSortTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2015 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 libcore.java.util;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+/**
+ * This test is based on test data generated by
+ * https://github.com/abstools/java-timsort-bug/blob/master/TestTimSort.java
+ */
+public class TimSortTest extends TestCase {
+
+  private static final Comparator<Integer> NATURAL_ORDER_COMPARATOR = new Comparator<Integer>() {
+    public int compare(Integer first, Integer second) {
+      return first.compareTo(second);
+    }
+  };
+
+  private static final int BAD_DATA_SIZE = 65536;
+
+  private static int[] BAD_RUN_OFFSETS = {
+      20204, 20221, 20237, 20255, 20289, 20363, 20521, 20837, 21469, 22733, 25260, 30315,
+      40408, 40425, 40441, 40459, 40493, 40567, 40725, 41041, 41673, 42936, 45463, 50500,
+      50517, 50533, 50551, 50585, 50659, 50817, 51133, 51764, 53027, 55536, 55553, 55569,
+      55587, 55621, 55695, 55853, 56168, 56799, 58044, 58061, 58077, 58095, 58129, 58203,
+      58360, 58675, 59288, 59305, 59321, 59339, 59373, 59446, 59603, 59900, 59917, 59933,
+      59951, 59985, 60059, 60196, 60217, 60236, 60274, 60332, 60351, 60369, 60389, 60405,
+  };
+
+  public void testBug19493779WithComparable() throws Exception {
+    Integer[] array = createBugTriggerData();
+    Arrays.sort(array);
+    // The bug caused an ArrayIndexOutOfBoundsException, but we check this anyway.
+    assertSorted(array);
+  }
+
+  public void testBug19493779WithComparator() throws Exception {
+    Integer[] array = createBugTriggerData();
+    Arrays.sort(array, NATURAL_ORDER_COMPARATOR);
+    // The bug caused an ArrayIndexOutOfBoundsException, but we check this anyway.
+    assertSorted(array);
+  }
+
+  private static void assertSorted(Integer[] arrayToSort) {
+    for (int i = 1; i < arrayToSort.length; i++) {
+      if (arrayToSort[i - 1] > arrayToSort[i]) {
+        fail("Array not sorted at element " + i + ": " + Arrays.toString(arrayToSort));
+      }
+    }
+  }
+
+  private static Integer[] createBugTriggerData() {
+    final Integer zero = 0;
+    final Integer one = 1;
+
+    Integer[] bugTriggerData = new Integer[BAD_DATA_SIZE];
+    for (int i = 0; i < bugTriggerData.length; i++) {
+      bugTriggerData[i] = zero;
+    }
+
+    for (int i = 0; i < BAD_RUN_OFFSETS.length; i++) {
+      bugTriggerData[BAD_RUN_OFFSETS[i]] = one;
+    }
+    return bugTriggerData;
+  }
+}
\ No newline at end of file
diff --git a/luni/src/test/java/libcore/java/util/prefs/OldAbstractPreferencesTest.java b/luni/src/test/java/libcore/java/util/prefs/OldAbstractPreferencesTest.java
index 693f0c2..0f8ed99 100644
--- a/luni/src/test/java/libcore/java/util/prefs/OldAbstractPreferencesTest.java
+++ b/luni/src/test/java/libcore/java/util/prefs/OldAbstractPreferencesTest.java
@@ -37,6 +37,12 @@
 
     static final String nodeName = "mock";
 
+    /** Timeout used for Object.wait when no action is expected. */
+    static final long NO_ACTION_EXPECTED_TIMEOUT = 200;
+
+    /** Timeout used for Object.wait when an action is expected. */
+    static final long ACTION_EXPECTED_TIMEOUT = 0; // Wait indefinitely
+
     private PreferencesFactory defaultFactory;
 
     AbstractPreferences pref;
@@ -885,7 +891,9 @@
         }
 
         public synchronized void assertChanged(boolean expected) throws InterruptedException {
-            wait(100);
+            if (!flagChange) {
+              wait(expected ? ACTION_EXPECTED_TIMEOUT : NO_ACTION_EXPECTED_TIMEOUT);
+            }
             assertEquals(expected, flagChange);
             flagChange = false;
         }
@@ -933,12 +941,16 @@
         }
 
         public synchronized void assertAdded(boolean expected) throws InterruptedException {
-            wait(100);
+            if (!flagAdded) {
+              wait(expected ? ACTION_EXPECTED_TIMEOUT : NO_ACTION_EXPECTED_TIMEOUT);
+            }
             assertEquals(expected, flagAdded);
         }
 
         public synchronized void assertRemoved(boolean expected) throws InterruptedException {
-            wait(100);
+            if (!flagRemoved) {
+              wait(expected ? ACTION_EXPECTED_TIMEOUT : NO_ACTION_EXPECTED_TIMEOUT);
+            }
             assertEquals(expected, flagRemoved);
         }
     }
diff --git a/luni/src/test/java/libcore/javax/crypto/CipherTest.java b/luni/src/test/java/libcore/javax/crypto/CipherTest.java
index 65ddb3c..73357c6 100644
--- a/luni/src/test/java/libcore/javax/crypto/CipherTest.java
+++ b/luni/src/test/java/libcore/javax/crypto/CipherTest.java
@@ -45,6 +45,7 @@
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
+import javax.crypto.AEADBadTagException;
 import javax.crypto.BadPaddingException;
 import javax.crypto.Cipher;
 import javax.crypto.IllegalBlockSizeException;
@@ -231,6 +232,10 @@
         return algorithm.startsWith("PBE");
     }
 
+    private static boolean isAEAD(String algorithm) {
+        return "GCM".equals(algorithm) || algorithm.contains("/GCM/");
+    }
+
     private static boolean isStreamMode(String algorithm) {
         return algorithm.contains("/CTR/") || algorithm.contains("/OFB")
                 || algorithm.contains("/CFB");
@@ -295,10 +300,10 @@
         setExpectedBlockSize("AES/ECB/PKCS5PADDING", 16);
         setExpectedBlockSize("AES/ECB/PKCS7PADDING", 16);
         setExpectedBlockSize("AES/ECB/NOPADDING", 16);
+        setExpectedBlockSize("AES/GCM/NOPADDING", 16);
         setExpectedBlockSize("AES/OFB/PKCS5PADDING", 16);
         setExpectedBlockSize("AES/OFB/PKCS7PADDING", 16);
         setExpectedBlockSize("AES/OFB/NOPADDING", 16);
-        setExpectedBlockSize("GCM", 16);
         setExpectedBlockSize("PBEWITHMD5AND128BITAES-CBC-OPENSSL", 16);
         setExpectedBlockSize("PBEWITHMD5AND192BITAES-CBC-OPENSSL", 16);
         setExpectedBlockSize("PBEWITHMD5AND256BITAES-CBC-OPENSSL", 16);
@@ -453,9 +458,9 @@
         setExpectedOutputSize("AES/CTS/PKCS7PADDING", Cipher.ENCRYPT_MODE, 16);
         setExpectedOutputSize("AES/ECB/PKCS5PADDING", Cipher.ENCRYPT_MODE, 16);
         setExpectedOutputSize("AES/ECB/PKCS7PADDING", Cipher.ENCRYPT_MODE, 16);
+        setExpectedOutputSize("AES/GCM/NOPADDING", Cipher.ENCRYPT_MODE, GCM_TAG_SIZE_BITS / 8);
         setExpectedOutputSize("AES/OFB/PKCS5PADDING", Cipher.ENCRYPT_MODE, 16);
         setExpectedOutputSize("AES/OFB/PKCS7PADDING", Cipher.ENCRYPT_MODE, 16);
-        setExpectedOutputSize("GCM", Cipher.ENCRYPT_MODE, GCM_TAG_SIZE_BITS / 8);
         setExpectedOutputSize("PBEWITHMD5AND128BITAES-CBC-OPENSSL", 16);
         setExpectedOutputSize("PBEWITHMD5AND192BITAES-CBC-OPENSSL", 16);
         setExpectedOutputSize("PBEWITHMD5AND256BITAES-CBC-OPENSSL", 16);
@@ -486,9 +491,9 @@
         setExpectedOutputSize("AES/CTS/PKCS7PADDING", Cipher.DECRYPT_MODE, 0);
         setExpectedOutputSize("AES/ECB/PKCS5PADDING", Cipher.DECRYPT_MODE, 0);
         setExpectedOutputSize("AES/ECB/PKCS7PADDING", Cipher.DECRYPT_MODE, 0);
+        setExpectedOutputSize("AES/GCM/NOPADDING", Cipher.DECRYPT_MODE, 0);
         setExpectedOutputSize("AES/OFB/PKCS5PADDING", Cipher.DECRYPT_MODE, 0);
         setExpectedOutputSize("AES/OFB/PKCS7PADDING", Cipher.DECRYPT_MODE, 0);
-        setExpectedOutputSize("GCM", Cipher.DECRYPT_MODE, 0);
         setExpectedOutputSize("PBEWITHMD5AND128BITAES-CBC-OPENSSL", Cipher.DECRYPT_MODE, 0);
         setExpectedOutputSize("PBEWITHMD5AND192BITAES-CBC-OPENSSL", Cipher.DECRYPT_MODE, 0);
         setExpectedOutputSize("PBEWITHMD5AND256BITAES-CBC-OPENSSL", Cipher.DECRYPT_MODE, 0);
@@ -750,8 +755,8 @@
             new SecureRandom().nextBytes(salt);
             return new PBEParameterSpec(salt, 1024);
         }
-        if (algorithm.equals("GCM")) {
-            final byte[] iv = new byte[8];
+        if (algorithm.equals("AES/GCM/NOPADDING")) {
+            final byte[] iv = new byte[12];
             new SecureRandom().nextBytes(iv);
             return new GCMParameterSpec(GCM_TAG_SIZE_BITS, iv);
         }
@@ -791,7 +796,7 @@
         }
         byte[] iv = encryptCipher.getIV();
         if (iv != null) {
-            if ("GCM".equals(algorithm)) {
+            if ("AES/GCM/NOPADDING".equals(algorithm)) {
                 return new GCMParameterSpec(GCM_TAG_SIZE_BITS, iv);
             }
             return new IvParameterSpec(iv);
@@ -988,9 +993,20 @@
 
         Security.addProvider(mockProviderInvalid);
         try {
-            Cipher.getInstance("FOO");
-            fail("Should not find any matching providers");
-        } catch (NoSuchAlgorithmException expected) {
+            Cipher c = Cipher.getInstance("FOO");
+            if (StandardNames.IS_RI) {
+                c.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(new byte[16], "FOO"));
+            } else {
+                fail("Should not find any matching providers; found: " + c);
+            }
+        } catch (NoSuchAlgorithmException maybe) {
+            if (StandardNames.IS_RI) {
+                throw maybe;
+            }
+        } catch (ClassCastException maybe) {
+            if (!StandardNames.IS_RI) {
+                throw maybe;
+            }
         } finally {
             Security.removeProvider(mockProviderInvalid.getName());
         }
@@ -1143,9 +1159,9 @@
 
         c.init(encryptMode, encryptKey, encryptSpec);
         assertEquals(cipherID + " getBlockSize() encryptMode",
-                     getExpectedBlockSize(algorithm, encryptMode, providerName), c.getBlockSize());
-        assertEquals(cipherID + " getOutputSize(0) encryptMode",
-                     getExpectedOutputSize(algorithm, encryptMode, providerName), c.getOutputSize(0));
+                getExpectedBlockSize(algorithm, encryptMode, providerName), c.getBlockSize());
+        assertTrue(cipherID + " getOutputSize(0) encryptMode",
+                getExpectedOutputSize(algorithm, encryptMode, providerName) <= c.getOutputSize(0));
         if ((algorithm.endsWith("/PKCS5PADDING") || algorithm.endsWith("/PKCS7PADDING"))
                 && isStreamMode(algorithm)) {
             assertEquals(getExpectedOutputSize(algorithm, encryptMode, providerName),
@@ -1209,7 +1225,7 @@
         // Test wrapping a key.  Every cipher should be able to wrap. Except those that can't.
         /* Bouncycastle is broken for wrapping because getIV() fails. */
         if (isSupportedForWrapping(algorithm)
-                && !algorithm.equals("GCM") && !providerName.equals("BC")) {
+                && !algorithm.equals("AES/GCM/NOPADDING") && !providerName.equals("BC")) {
             // Generate a small SecretKey for AES.
             KeyGenerator kg = KeyGenerator.getInstance("AES");
             kg.init(128);
@@ -1233,16 +1249,28 @@
 
         if (!isOnlyWrappingAlgorithm(algorithm)) {
             c.init(Cipher.ENCRYPT_MODE, encryptKey, encryptSpec);
+            if (isAEAD(algorithm)) {
+                c.updateAAD(new byte[24]);
+            }
             byte[] cipherText = c.doFinal(getActualPlainText(algorithm));
+            if (isAEAD(algorithm)) {
+                c.updateAAD(new byte[24]);
+            }
             byte[] cipherText2 = c.doFinal(getActualPlainText(algorithm));
             assertEquals(cipherID,
                          Arrays.toString(cipherText),
                          Arrays.toString(cipherText2));
             c.init(Cipher.DECRYPT_MODE, getDecryptKey(algorithm), decryptSpec);
+            if (isAEAD(algorithm)) {
+                c.updateAAD(new byte[24]);
+            }
             byte[] decryptedPlainText = c.doFinal(cipherText);
             assertEquals(cipherID,
                          Arrays.toString(getExpectedPlainText(algorithm, providerName)),
                          Arrays.toString(decryptedPlainText));
+            if (isAEAD(algorithm)) {
+                c.updateAAD(new byte[24]);
+            }
             byte[] decryptedPlainText2 = c.doFinal(cipherText);
             assertEquals(cipherID,
                          Arrays.toString(decryptedPlainText),
@@ -2502,6 +2530,80 @@
     };
 
     /*
+     * Taken from BoringSSL test vectors.
+     */
+    private static final byte[] AES_128_GCM_TestVector_1_Key = new byte[] {
+            (byte) 0xca, (byte) 0xbd, (byte) 0xcf, (byte) 0x54, (byte) 0x1a, (byte) 0xeb,
+            (byte) 0xf9, (byte) 0x17, (byte) 0xba, (byte) 0xc0, (byte) 0x19, (byte) 0xf1,
+            (byte) 0x39, (byte) 0x25, (byte) 0xd2, (byte) 0x67,
+    };
+
+    /*
+     * Taken from BoringSSL test vectors.
+     */
+    private static final byte[] AES_128_GCM_TestVector_1_IV = new byte[] {
+            (byte) 0x2c, (byte) 0x34, (byte) 0xc0, (byte) 0x0c, (byte) 0x42, (byte) 0xda,
+            (byte) 0xe3, (byte) 0x82, (byte) 0x27, (byte) 0x9d, (byte) 0x79, (byte) 0x74,
+    };
+
+    /*
+     * Taken from BoringSSL test vectors.
+     */
+    private static final byte[] AES_128_GCM_TestVector_1_AAD = new byte[] {
+            (byte) 0xdd, (byte) 0x10, (byte) 0xe3, (byte) 0x71, (byte) 0xb2, (byte) 0x2e,
+            (byte) 0x15, (byte) 0x67, (byte) 0x1c, (byte) 0x31, (byte) 0xaf, (byte) 0xee,
+            (byte) 0x55, (byte) 0x2b, (byte) 0xf1, (byte) 0xde, (byte) 0xa0, (byte) 0x7c,
+            (byte) 0xbb, (byte) 0xf6, (byte) 0x85, (byte) 0xe2, (byte) 0xca, (byte) 0xa0,
+            (byte) 0xe0, (byte) 0x36, (byte) 0x37, (byte) 0x16, (byte) 0xa2, (byte) 0x76,
+            (byte) 0xe1, (byte) 0x20, (byte) 0xc6, (byte) 0xc0, (byte) 0xeb, (byte) 0x4a,
+            (byte) 0xcb, (byte) 0x1a, (byte) 0x4d, (byte) 0x1b, (byte) 0xa7, (byte) 0x3f,
+            (byte) 0xde, (byte) 0x66, (byte) 0x15, (byte) 0xf7, (byte) 0x08, (byte) 0xaa,
+            (byte) 0xa4, (byte) 0x6b, (byte) 0xc7, (byte) 0x6c, (byte) 0x7f, (byte) 0xf3,
+            (byte) 0x45, (byte) 0xa4, (byte) 0xf7, (byte) 0x6b, (byte) 0xda, (byte) 0x11,
+            (byte) 0x7f, (byte) 0xe5, (byte) 0x6f, (byte) 0x0d, (byte) 0xc9, (byte) 0xb9,
+            (byte) 0x39, (byte) 0x04, (byte) 0x0d, (byte) 0xdd,
+    };
+
+    /*
+     * Taken from BoringSSL test vectors.
+     */
+    private static final byte[] AES_128_GCM_TestVector_1_Plaintext = new byte[] {
+            (byte) 0x88, (byte) 0xcc, (byte) 0x1e, (byte) 0x07, (byte) 0xdf, (byte) 0xde,
+            (byte) 0x8e, (byte) 0x08, (byte) 0x08, (byte) 0x2e, (byte) 0x67, (byte) 0x66,
+            (byte) 0xe0, (byte) 0xa8, (byte) 0x81, (byte) 0x03, (byte) 0x38, (byte) 0x47,
+            (byte) 0x42, (byte) 0xaf, (byte) 0x37, (byte) 0x8d, (byte) 0x7b, (byte) 0x6b,
+            (byte) 0x8a, (byte) 0x87, (byte) 0xfc, (byte) 0xe0, (byte) 0x36, (byte) 0xaf,
+            (byte) 0x74, (byte) 0x41, (byte) 0xc1, (byte) 0x39, (byte) 0x61, (byte) 0xc2,
+            (byte) 0x5a, (byte) 0xfe, (byte) 0xa7, (byte) 0xf6, (byte) 0xe5, (byte) 0x61,
+            (byte) 0x93, (byte) 0xf5, (byte) 0x4b, (byte) 0xee, (byte) 0x00, (byte) 0x11,
+            (byte) 0xcb, (byte) 0x78, (byte) 0x64, (byte) 0x2c, (byte) 0x3a, (byte) 0xb9,
+            (byte) 0xe6, (byte) 0xd5, (byte) 0xb2, (byte) 0xe3, (byte) 0x58, (byte) 0x33,
+            (byte) 0xec, (byte) 0x16, (byte) 0xcd, (byte) 0x35, (byte) 0x55, (byte) 0x15,
+            (byte) 0xaf, (byte) 0x1a, (byte) 0x19, (byte) 0x0f,
+    };
+
+    /*
+     * Taken from BoringSSL test vectors.
+     */
+    private static final byte[] AES_128_GCM_TestVector_1_Encrypted = new byte[] {
+            (byte) 0x04, (byte) 0x94, (byte) 0x53, (byte) 0xba, (byte) 0xf1, (byte) 0x57,
+            (byte) 0x87, (byte) 0x87, (byte) 0xd6, (byte) 0x8e, (byte) 0xd5, (byte) 0x47,
+            (byte) 0x87, (byte) 0x26, (byte) 0xc0, (byte) 0xb8, (byte) 0xa6, (byte) 0x36,
+            (byte) 0x33, (byte) 0x7a, (byte) 0x0b, (byte) 0x8a, (byte) 0x82, (byte) 0xb8,
+            (byte) 0x68, (byte) 0x36, (byte) 0xf9, (byte) 0x1c, (byte) 0xde, (byte) 0x25,
+            (byte) 0xe6, (byte) 0xe4, (byte) 0x4c, (byte) 0x34, (byte) 0x59, (byte) 0x40,
+            (byte) 0xe8, (byte) 0x19, (byte) 0xa0, (byte) 0xc5, (byte) 0x05, (byte) 0x75,
+            (byte) 0x1e, (byte) 0x60, (byte) 0x3c, (byte) 0xb8, (byte) 0xf8, (byte) 0xc4,
+            (byte) 0xfe, (byte) 0x98, (byte) 0x71, (byte) 0x91, (byte) 0x85, (byte) 0x56,
+            (byte) 0x27, (byte) 0x94, (byte) 0xa1, (byte) 0x85, (byte) 0xe5, (byte) 0xde,
+            (byte) 0xc4, (byte) 0x15, (byte) 0xc8, (byte) 0x1f, (byte) 0x2f, (byte) 0x16,
+            (byte) 0x2c, (byte) 0xdc, (byte) 0xd6, (byte) 0x50, (byte) 0xdc, (byte) 0xe7,
+            (byte) 0x19, (byte) 0x87, (byte) 0x28, (byte) 0xbf, (byte) 0xc1, (byte) 0xb5,
+            (byte) 0xf9, (byte) 0x49, (byte) 0xb9, (byte) 0xb5, (byte) 0x37, (byte) 0x41,
+            (byte) 0x99, (byte) 0xc6,
+    };
+
+    /*
      * Test key generation:
      * openssl rand -hex 16
      * echo 'ceaa31952dfd3d0f5af4b2042ba06094' | sed 's/\(..\)/(byte) 0x\1, /g'
@@ -2570,17 +2672,20 @@
 
         public final byte[] iv;
 
+        public final byte[] aad;
+
         public final byte[] plaintext;
 
         public final byte[] ciphertext;
 
         public final byte[] plaintextPadded;
 
-        public CipherTestParam(String transformation, byte[] key, byte[] iv, byte[] plaintext,
-                byte[] plaintextPadded, byte[] ciphertext) {
-            this.transformation = transformation;
+        public CipherTestParam(String transformation, byte[] key, byte[] iv, byte[] aad,
+                byte[] plaintext, byte[] plaintextPadded, byte[] ciphertext) {
+            this.transformation = transformation.toUpperCase(Locale.ROOT);
             this.key = key;
             this.iv = iv;
+            this.aad = aad;
             this.plaintext = plaintext;
             this.plaintextPadded = plaintextPadded;
             this.ciphertext = ciphertext;
@@ -2591,23 +2696,34 @@
     static {
         CIPHER_TEST_PARAMS.add(new CipherTestParam("AES/ECB/PKCS5Padding", AES_128_KEY,
                 null,
+                null,
                 AES_128_ECB_PKCS5Padding_TestVector_1_Plaintext,
                 AES_128_ECB_PKCS5Padding_TestVector_1_Plaintext_Padded,
                 AES_128_ECB_PKCS5Padding_TestVector_1_Encrypted));
         // PKCS#5 is assumed to be equivalent to PKCS#7 -- same test vectors are thus used for both.
         CIPHER_TEST_PARAMS.add(new CipherTestParam("AES/ECB/PKCS7Padding", AES_128_KEY,
                 null,
+                null,
                 AES_128_ECB_PKCS5Padding_TestVector_1_Plaintext,
                 AES_128_ECB_PKCS5Padding_TestVector_1_Plaintext_Padded,
                 AES_128_ECB_PKCS5Padding_TestVector_1_Encrypted));
+        CIPHER_TEST_PARAMS.add(new CipherTestParam("AES/GCM/NOPADDING",
+                AES_128_GCM_TestVector_1_Key,
+                AES_128_GCM_TestVector_1_IV,
+                AES_128_GCM_TestVector_1_AAD,
+                AES_128_GCM_TestVector_1_Plaintext,
+                AES_128_GCM_TestVector_1_Plaintext,
+                AES_128_GCM_TestVector_1_Encrypted));
         if (IS_UNLIMITED) {
             CIPHER_TEST_PARAMS.add(new CipherTestParam("AES/CBC/PKCS5Padding", AES_256_KEY,
                     AES_256_CBC_PKCS5Padding_TestVector_1_IV,
+                    null,
                     AES_256_CBC_PKCS5Padding_TestVector_1_Plaintext,
                     AES_256_CBC_PKCS5Padding_TestVector_1_Plaintext_Padded,
                     AES_256_CBC_PKCS5Padding_TestVector_1_Ciphertext));
             CIPHER_TEST_PARAMS.add(new CipherTestParam("AES/CBC/PKCS7Padding", AES_256_KEY,
                     AES_256_CBC_PKCS5Padding_TestVector_1_IV,
+                    null,
                     AES_256_CBC_PKCS5Padding_TestVector_1_Plaintext,
                     AES_256_CBC_PKCS5Padding_TestVector_1_Plaintext_Padded,
                     AES_256_CBC_PKCS5Padding_TestVector_1_Ciphertext));
@@ -2643,62 +2759,98 @@
     private void checkCipher(CipherTestParam p, String provider) throws Exception {
         SecretKey key = new SecretKeySpec(p.key, "AES");
         Cipher c = Cipher.getInstance(p.transformation, provider);
+
         AlgorithmParameterSpec spec = null;
         if (p.iv != null) {
-            spec = new IvParameterSpec(p.iv);
+            if (isAEAD(p.transformation)) {
+                spec = new GCMParameterSpec((p.ciphertext.length - p.plaintext.length) * 8, p.iv);
+            } else {
+                spec = new IvParameterSpec(p.iv);
+            }
         }
+
         c.init(Cipher.ENCRYPT_MODE, key, spec);
 
+        if (p.aad != null) {
+            c.updateAAD(p.aad);
+        }
         final byte[] actualCiphertext = c.doFinal(p.plaintext);
-        assertEquals(Arrays.toString(p.ciphertext), Arrays.toString(actualCiphertext));
+        assertEquals(p.transformation + " " + provider, Arrays.toString(p.ciphertext),
+                Arrays.toString(actualCiphertext));
 
+        c = Cipher.getInstance(p.transformation, provider);
+        c.init(Cipher.ENCRYPT_MODE, key, spec);
         byte[] emptyCipherText = c.doFinal();
         assertNotNull(emptyCipherText);
 
         c.init(Cipher.DECRYPT_MODE, key, spec);
 
-        try {
-            c.updateAAD(new byte[8]);
-            fail("Cipher should not support AAD");
-        } catch (UnsupportedOperationException expected) {
+        if (!isAEAD(p.transformation)) {
+            try {
+                c.updateAAD(new byte[8]);
+                fail("Cipher should not support AAD");
+            } catch (UnsupportedOperationException | IllegalStateException expected) {
+            }
         }
 
-        byte[] emptyPlainText = c.doFinal(emptyCipherText);
-        assertEquals(Arrays.toString(new byte[0]), Arrays.toString(emptyPlainText));
+        try {
+            byte[] emptyPlainText = c.doFinal(emptyCipherText);
+            assertEquals(Arrays.toString(new byte[0]), Arrays.toString(emptyPlainText));
+        } catch (AEADBadTagException e) {
+            if (!"AndroidOpenSSL".equals(provider) || !isAEAD(p.transformation)) {
+                throw e;
+            }
+        }
 
         // empty decrypt
         {
-            if (StandardNames.IS_RI) {
+            if (!isAEAD(p.transformation)
+                    && (StandardNames.IS_RI || provider.equals("AndroidOpenSSL"))) {
                 assertEquals(Arrays.toString(new byte[0]),
                              Arrays.toString(c.doFinal()));
 
                 c.update(new byte[0]);
                 assertEquals(Arrays.toString(new byte[0]),
                              Arrays.toString(c.doFinal()));
-            } else if (provider.equals("BC")) {
+            } else if (provider.equals("BC") || isAEAD(p.transformation)) {
                 try {
                     c.doFinal();
                     fail();
-                } catch (IllegalBlockSizeException expected) {
+                } catch (IllegalBlockSizeException maybe) {
+                    if (isAEAD(p.transformation)) {
+                        throw maybe;
+                    }
+                } catch (AEADBadTagException maybe) {
+                    if (!isAEAD(p.transformation)) {
+                        throw maybe;
+                    }
                 }
                 try {
                     c.update(new byte[0]);
                     c.doFinal();
                     fail();
-                } catch (IllegalBlockSizeException expected) {
+                } catch (IllegalBlockSizeException maybe) {
+                    if (isAEAD(p.transformation)) {
+                        throw maybe;
+                    }
+                } catch (AEADBadTagException maybe) {
+                    if (!isAEAD(p.transformation)) {
+                        throw maybe;
+                    }
                 }
-            } else if (provider.equals("AndroidOpenSSL")) {
-                assertNull(c.doFinal());
-
-                c.update(new byte[0]);
-                assertNull(c.doFinal());
             } else {
                 throw new AssertionError("Define your behavior here for " + provider);
             }
         }
 
+        // Cipher might be in unspecified state from failures above.
+        c.init(Cipher.DECRYPT_MODE, key, spec);
+
         // .doFinal(input)
         {
+            if (p.aad != null) {
+                c.updateAAD(p.aad);
+            }
             final byte[] actualPlaintext = c.doFinal(p.ciphertext);
             assertEquals(Arrays.toString(p.plaintext), Arrays.toString(actualPlaintext));
         }
@@ -2708,6 +2860,12 @@
             final byte[] largerThanCiphertext = new byte[p.ciphertext.length + 5];
             System.arraycopy(p.ciphertext, 0, largerThanCiphertext, 5, p.ciphertext.length);
 
+            if (p.aad != null) {
+                final byte[] largerThanAad = new byte[p.aad.length + 100];
+                System.arraycopy(p.aad, 0, largerThanAad, 50, p.aad.length);
+                c.updateAAD(largerThanAad, 50, p.aad.length);
+            }
+
             final byte[] actualPlaintext = new byte[c.getOutputSize(p.ciphertext.length)];
             assertEquals(p.plaintext.length,
                     c.doFinal(largerThanCiphertext, 5, p.ciphertext.length, actualPlaintext));
@@ -2720,6 +2878,12 @@
             final byte[] largerThanCiphertext = new byte[p.ciphertext.length + 10];
             System.arraycopy(p.ciphertext, 0, largerThanCiphertext, 5, p.ciphertext.length);
 
+            if (p.aad != null) {
+                final byte[] largerThanAad = new byte[p.aad.length + 2];
+                System.arraycopy(p.aad, 0, largerThanAad, 2, p.aad.length);
+                c.updateAAD(largerThanAad, 2, p.aad.length);
+            }
+
             final byte[] actualPlaintext = new byte[c.getOutputSize(p.ciphertext.length) + 2];
             assertEquals(p.plaintext.length,
                     c.doFinal(largerThanCiphertext, 5, p.ciphertext.length, actualPlaintext, 1));
@@ -2727,13 +2891,18 @@
                     Arrays.toString(Arrays.copyOfRange(actualPlaintext, 1, p.plaintext.length + 1)));
         }
 
-        Cipher cNoPad = Cipher.getInstance(
-                getCipherTransformationWithNoPadding(p.transformation), provider);
-        cNoPad.init(Cipher.DECRYPT_MODE, key, spec);
+        if (!p.transformation.endsWith("NOPADDING")) {
+            Cipher cNoPad = Cipher.getInstance(
+                    getCipherTransformationWithNoPadding(p.transformation), provider);
+            cNoPad.init(Cipher.DECRYPT_MODE, key, spec);
 
-        final byte[] actualPlaintextPadded = cNoPad.doFinal(p.ciphertext);
-        assertEquals(provider + ":" + cNoPad.getAlgorithm(), Arrays.toString(p.plaintextPadded),
-                Arrays.toString(actualPlaintextPadded));
+            if (p.aad != null) {
+                c.updateAAD(p.aad);
+            }
+            final byte[] actualPlaintextPadded = cNoPad.doFinal(p.ciphertext);
+            assertEquals(provider + ":" + cNoPad.getAlgorithm(),
+                    Arrays.toString(p.plaintextPadded), Arrays.toString(actualPlaintextPadded));
+        }
 
         // Test wrapping a key. Every cipher should be able to wrap.
         {
@@ -2743,6 +2912,7 @@
             SecretKey sk = kg.generateKey();
 
             // Wrap it
+            c = Cipher.getInstance(p.transformation, provider);
             c.init(Cipher.WRAP_MODE, key, spec);
             byte[] cipherText = c.wrap(sk);
 
@@ -2849,6 +3019,27 @@
             fail("should not be able to call updateAAD with too large length");
         } catch (IllegalArgumentException expected) {
         }
+
+        try {
+            c.updateAAD(new byte[8]);
+            fail("should not be able to call updateAAD on non-AEAD cipher");
+        } catch (UnsupportedOperationException | IllegalStateException expected) {
+        }
+    }
+
+    public void testCipher_updateAAD_AfterInit_WithGcm_Success() throws Exception {
+        Cipher c = Cipher.getInstance("AES/GCM/NoPadding");
+        c.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(new byte[128 / 8], "AES"));
+        c.updateAAD(new byte[8]);
+        c.updateAAD(new byte[8]);
+    }
+
+    public void testCipher_updateAAD_AfterUpdate_WithGcm_Sucess() throws Exception {
+        Cipher c = Cipher.getInstance("AES/GCM/NoPadding");
+        c.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(new byte[128 / 8], "AES"));
+        c.updateAAD(new byte[8]);
+        c.update(new byte[8]);
+        c.updateAAD(new byte[8]);
     }
 
     public void testCipher_ShortBlock_Failure() throws Exception {
@@ -2888,6 +3079,12 @@
     }
 
     private void checkCipher_ShortBlock_Failure(CipherTestParam p, String provider) throws Exception {
+        // Do not try to test ciphers with no padding already.
+        String noPaddingTransform = getCipherTransformationWithNoPadding(p.transformation);
+        if (p.transformation.equals(noPaddingTransform)) {
+            return;
+        }
+
         SecretKey key = new SecretKeySpec(p.key, "AES");
         Cipher c = Cipher.getInstance(
                 getCipherTransformationWithNoPadding(p.transformation), provider);
@@ -2895,12 +3092,14 @@
             return;
         }
 
-        c.init(Cipher.ENCRYPT_MODE, key);
-        try {
-            c.doFinal(new byte[] { 0x01, 0x02, 0x03 });
-            fail("Should throw IllegalBlockSizeException on wrong-sized block; provider="
-                    + provider);
-        } catch (IllegalBlockSizeException expected) {
+        if (!p.transformation.endsWith("NOPADDING")) {
+            c.init(Cipher.ENCRYPT_MODE, key);
+            try {
+                c.doFinal(new byte[] { 0x01, 0x02, 0x03 });
+                fail("Should throw IllegalBlockSizeException on wrong-sized block; transform="
+                        + p.transformation + " provider=" + provider);
+            } catch (IllegalBlockSizeException expected) {
+            }
         }
     }