Merge "Late binding: supplied Provider should be used"
diff --git a/luni/src/main/java/javax/crypto/Cipher.java b/luni/src/main/java/javax/crypto/Cipher.java
index 6299f80..91789b4 100644
--- a/luni/src/main/java/javax/crypto/Cipher.java
+++ b/luni/src/main/java/javax/crypto/Cipher.java
@@ -397,50 +397,65 @@
 
     private static Engine.SpiAndProvider tryTransform(Key key, Provider provider, String transform,
             String[] transformParts, NeedToSet type) {
-        Engine.SpiAndProvider sap;
-        ArrayList<Provider.Service> services = ENGINE.getServices(transform, provider);
+        if (provider != null) {
+            Provider.Service service = provider.getService(SERVICE, transform);
+            if (service == null) {
+                return null;
+            }
+            return tryTransformWithProvider(key, transformParts, type, service);
+        }
+        ArrayList<Provider.Service> services = ENGINE.getServices(transform);
         if (services == null) {
             return null;
         }
         for (Provider.Service service : services) {
-            try {
-                if (key != null && !service.supportsParameter(key)) {
-                    continue;
-                }
-
-                /*
-                 * Check to see if the Cipher even supports the attributes
-                 * before trying to instantiate it.
-                 */
-                if (!matchAttribute(service, ATTRIBUTE_MODES, transformParts[1])
-                        || !matchAttribute(service, ATTRIBUTE_PADDINGS, transformParts[2])) {
-                    continue;
-                }
-
-                sap = ENGINE.getInstance(service, null);
-                if (sap.spi == null || sap.provider == null) {
-                    continue;
-                }
-                if (!(sap.spi instanceof CipherSpi)) {
-                    continue;
-                }
-                CipherSpi spi = (CipherSpi) sap.spi;
-                if (((type == NeedToSet.MODE) || (type == NeedToSet.BOTH))
-                        && (transformParts[1] != null)) {
-                    spi.engineSetMode(transformParts[1]);
-                }
-                if (((type == NeedToSet.PADDING) || (type == NeedToSet.BOTH))
-                        && (transformParts[2] != null)) {
-                    spi.engineSetPadding(transformParts[2]);
-                }
+            Engine.SpiAndProvider sap = tryTransformWithProvider(key, transformParts, type, service);
+            if (sap != null) {
                 return sap;
-            } catch (NoSuchAlgorithmException ignored) {
-            } catch (NoSuchPaddingException ignored) {
             }
         }
         return null;
     }
 
+    private static Engine.SpiAndProvider tryTransformWithProvider(Key key, String[] transformParts,
+            NeedToSet type, Provider.Service service) {
+        try {
+            if (key != null && !service.supportsParameter(key)) {
+                return null;
+            }
+
+            /*
+             * Check to see if the Cipher even supports the attributes before
+             * trying to instantiate it.
+             */
+            if (!matchAttribute(service, ATTRIBUTE_MODES, transformParts[1])
+                    || !matchAttribute(service, ATTRIBUTE_PADDINGS, transformParts[2])) {
+                return null;
+            }
+
+            Engine.SpiAndProvider sap = ENGINE.getInstance(service, null);
+            if (sap.spi == null || sap.provider == null) {
+                return sap;
+            }
+            if (!(sap.spi instanceof CipherSpi)) {
+                return sap;
+            }
+            CipherSpi spi = (CipherSpi) sap.spi;
+            if (((type == NeedToSet.MODE) || (type == NeedToSet.BOTH))
+                    && (transformParts[1] != null)) {
+                spi.engineSetMode(transformParts[1]);
+            }
+            if (((type == NeedToSet.PADDING) || (type == NeedToSet.BOTH))
+                    && (transformParts[2] != null)) {
+                spi.engineSetPadding(transformParts[2]);
+            }
+            return sap;
+        } catch (NoSuchAlgorithmException ignored) {
+        } catch (NoSuchPaddingException ignored) {
+        }
+        return null;
+    }
+
     /**
      * If the attribute listed exists, check that it matches the regular
      * expression.
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 b9954bc..006dda8 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
@@ -26,7 +26,6 @@
 import java.security.Provider;
 import java.util.ArrayList;
 import java.util.Locale;
-import java.util.Objects;
 
 /**
  * This class implements common functionality for Provider supplied
@@ -93,19 +92,15 @@
     private static final class ServiceCacheEntry {
         /** used to test for cache hit */
         private final String algorithm;
-        /** used to test for cache hit */
-        private final Provider provider;
         /** used to test for cache validity */
         private final int cacheVersion;
         /** cached result */
         private final ArrayList<Provider.Service> services;
 
         private ServiceCacheEntry(String algorithm,
-                                  Provider provider,
                                   int cacheVersion,
                                   ArrayList<Provider.Service> services) {
             this.algorithm = algorithm;
-            this.provider = provider;
             this.cacheVersion = cacheVersion;
             this.services = services;
         }
@@ -139,7 +134,7 @@
         if (algorithm == null) {
             throw new NoSuchAlgorithmException("Null algorithm name");
         }
-        ArrayList<Provider.Service> services = getServices(algorithm, null);
+        ArrayList<Provider.Service> services = getServices(algorithm);
         if (services == null) {
             throw notFound(this.serviceName, algorithm);
         }
@@ -159,32 +154,19 @@
     /**
      * Returns a list of all possible matches for a given algorithm.
      */
-    public ArrayList<Provider.Service> getServices(String algorithm, Provider provider) {
+    public ArrayList<Provider.Service> getServices(String algorithm) {
         int newCacheVersion = Services.getCacheVersion();
         ServiceCacheEntry cacheEntry = this.serviceCache;
         final String algoUC = algorithm.toUpperCase(Locale.US);
         if (cacheEntry != null
                 && cacheEntry.algorithm.equalsIgnoreCase(algoUC)
-                && Objects.equals(cacheEntry.provider, provider)
                 && newCacheVersion == cacheEntry.cacheVersion) {
             return cacheEntry.services;
         }
         String name = this.serviceName + "." + algoUC;
         ArrayList<Provider.Service> services = Services.getServices(name);
-        if (provider == null || services == null) {
-            this.serviceCache = new ServiceCacheEntry(algoUC, provider, newCacheVersion, services);
-            return services;
-        }
-        ArrayList<Provider.Service> filteredServices = new ArrayList<Provider.Service>(
-                services.size());
-        for (Provider.Service service : services) {
-            if (provider.equals(service.getProvider())) {
-                filteredServices.add(service);
-            }
-        }
-        this.serviceCache = new ServiceCacheEntry(algoUC, provider, newCacheVersion,
-                filteredServices);
-        return filteredServices;
+        this.serviceCache = new ServiceCacheEntry(algoUC, newCacheVersion, services);
+        return services;
     }
 
     /**
diff --git a/luni/src/test/java/libcore/javax/crypto/CipherTest.java b/luni/src/test/java/libcore/javax/crypto/CipherTest.java
index b9241c0..21cc5da 100644
--- a/luni/src/test/java/libcore/javax/crypto/CipherTest.java
+++ b/luni/src/test/java/libcore/javax/crypto/CipherTest.java
@@ -26,6 +26,7 @@
 import java.security.InvalidKeyException;
 import java.security.Key;
 import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
 import java.security.Provider;
 import java.security.PublicKey;
@@ -832,6 +833,59 @@
         public abstract void setup();
     }
 
+    public void testCipher_getInstance_SuppliedProviderNotRegistered_Success() throws Exception {
+        Provider mockProvider = new MockProvider("MockProvider") {
+            public void setup() {
+                put("Cipher.FOO", MockCipherSpi.AllKeyTypes.class.getName());
+            }
+        };
+
+        {
+            Cipher c = Cipher.getInstance("FOO", mockProvider);
+            c.init(Cipher.ENCRYPT_MODE, new MockKey());
+            assertEquals(mockProvider, c.getProvider());
+        }
+    }
+
+    public void testCipher_getInstance_SuppliedProviderNotRegistered_MultipartTransform_Success()
+            throws Exception {
+        Provider mockProvider = new MockProvider("MockProvider") {
+            public void setup() {
+                put("Cipher.FOO", MockCipherSpi.AllKeyTypes.class.getName());
+            }
+        };
+
+        {
+            Cipher c = Cipher.getInstance("FOO/FOO/FOO", mockProvider);
+            c.init(Cipher.ENCRYPT_MODE, new MockKey());
+            assertEquals(mockProvider, c.getProvider());
+        }
+    }
+
+    public void testCipher_getInstance_OnlyUsesSpecifiedProvider_SameNameAndClass_Success()
+            throws Exception {
+        Provider mockProvider = new MockProvider("MockProvider") {
+            public void setup() {
+                put("Cipher.FOO", MockCipherSpi.AllKeyTypes.class.getName());
+            }
+        };
+
+        Security.addProvider(mockProvider);
+        try {
+            {
+                Provider mockProvider2 = new MockProvider("MockProvider") {
+                    public void setup() {
+                        put("Cipher.FOO", MockCipherSpi.AllKeyTypes.class.getName());
+                    }
+                };
+                Cipher c = Cipher.getInstance("FOO", mockProvider2);
+                assertEquals(mockProvider2, c.getProvider());
+            }
+        } finally {
+            Security.removeProvider(mockProvider.getName());
+        }
+    }
+
     public void testCipher_getInstance_DelayedInitialization_KeyType() throws Exception {
         Provider mockProviderSpecific = new MockProvider("MockProviderSpecific") {
             public void setup() {
diff --git a/luni/src/test/java/libcore/javax/crypto/MockCipherSpi.java b/luni/src/test/java/libcore/javax/crypto/MockCipherSpi.java
index b09d883..eb950cc 100644
--- a/luni/src/test/java/libcore/javax/crypto/MockCipherSpi.java
+++ b/luni/src/test/java/libcore/javax/crypto/MockCipherSpi.java
@@ -51,12 +51,16 @@
 
     @Override
     protected void engineSetMode(String mode) throws NoSuchAlgorithmException {
-        throw new UnsupportedOperationException("not implemented");
+        if (!"FOO".equals(mode)) {
+            throw new UnsupportedOperationException("not implemented");
+        }
     }
 
     @Override
     protected void engineSetPadding(String padding) throws NoSuchPaddingException {
-        throw new UnsupportedOperationException("not implemented");
+        if (!"FOO".equals(padding)) {
+            throw new UnsupportedOperationException("not implemented");
+        }
     }
 
     @Override