am bc864b48: Merge "Cipher: iterate through services first"

* commit 'bc864b48689ac9374ec88ab0d8a719666dc8a8e6':
  Cipher: iterate through services first
diff --git a/luni/src/main/java/javax/crypto/Cipher.java b/luni/src/main/java/javax/crypto/Cipher.java
index b27ea88..c455119 100644
--- a/luni/src/main/java/javax/crypto/Cipher.java
+++ b/luni/src/main/java/javax/crypto/Cipher.java
@@ -512,8 +512,9 @@
         // Try each of the transforms and keep track of the first exception
         // encountered.
         Exception cause = null;
-        for (Transform transform : transforms) {
-            if (provider != null) {
+
+        if (provider != null) {
+            for (Transform transform : transforms) {
                 Provider.Service service = provider.getService(SERVICE, transform.name);
                 if (service == null) {
                     continue;
@@ -521,22 +522,40 @@
                 return tryTransformWithProvider(initParams, transformParts, transform.needToSet,
                         service);
             }
-            ArrayList<Provider.Service> services = ENGINE.getServices(transform.name);
-            if (services == null || services.isEmpty()) {
-                continue;
-            }
+        } else {
+            ArrayList<Provider.Service> services = ENGINE.getServices();
             for (Provider.Service service : services) {
-                if (initParams == null || initParams.key == null
-                        || service.supportsParameter(initParams.key)) {
-                    try {
-                        Engine.SpiAndProvider sap = tryTransformWithProvider(initParams,
-                                transformParts, transform.needToSet, service);
-                        if (sap != null) {
-                            return sap;
+                String serviceAlgorithmUC = service.getAlgorithm().toUpperCase(Locale.US);
+                for (Transform transform : transforms) {
+                    // Check that this service offers the algorithm we're after
+                    // since none of the services have been filtered yet.
+                    boolean matchesAlgorithm = false;
+                    if (transform.name.equals(serviceAlgorithmUC)) {
+                        matchesAlgorithm = true;
+                    } else {
+                        for (String alias : Engine.door.getAliases(service)) {
+                            if (transform.name.equals(alias.toUpperCase(Locale.US))) {
+                                matchesAlgorithm = true;
+                                break;
+                            }
                         }
-                    } catch (Exception e) {
-                        if (cause == null) {
-                            cause = e;
+                    }
+                    if (!matchesAlgorithm) {
+                        continue;
+                    }
+
+                    if (initParams == null || initParams.key == null
+                            || service.supportsParameter(initParams.key)) {
+                        try {
+                            Engine.SpiAndProvider sap = tryTransformWithProvider(initParams,
+                                    transformParts, transform.needToSet, service);
+                            if (sap != null) {
+                                return sap;
+                            }
+                        } catch (Exception e) {
+                            if (cause == null) {
+                                cause = e;
+                            }
                         }
                     }
                 }
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 1c794e5..3c929c4 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,6 +152,14 @@
     }
 
     /**
+     * Returns a list of all providers for this type of service or {@code null}
+     * if no matches were found.
+     */
+    public ArrayList<Provider.Service> getServices() {
+        return Services.getServices(serviceName);
+    }
+
+    /**
      * Returns a list of all possible matches for a given algorithm. Returns
      * {@code null} if no matches were found.
      */
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 234f4a2..232b6d2 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
@@ -21,7 +21,7 @@
 import java.security.Security;
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.Locale;
+import java.util.Iterator;
 
 
 /**
@@ -134,12 +134,37 @@
     }
 
     /**
+     * Looks up the requested service by type. The service {@code type} and
+     * should be provided in the same format used when registering a service
+     * with a provider, for example, "KeyFactory". 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 of the given {@code type} found.
+     */
+    public static synchronized ArrayList<Provider.Service> getServices(String type) {
+        ArrayList<Provider.Service> services = null;
+        for (Provider p : providers) {
+            Iterator<Provider.Service> i = p.getServices().iterator();
+            while (i.hasNext()) {
+                Provider.Service s = i.next();
+                if (type.equals(s.getType())) {
+                    if (services == null) {
+                        services = new ArrayList<>(providers.size());
+                    }
+                    services.add(s);
+                }
+            }
+        }
+        return services;
+    }
+
+    /**
      * 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.
+     * registering a service with a provider, for example, "KeyFactory" and
+     * "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 ArrayList<Provider.Service> getServices(String type,
             String algorithm) {
diff --git a/luni/src/test/java/libcore/javax/crypto/CipherTest.java b/luni/src/test/java/libcore/javax/crypto/CipherTest.java
index 13d54b4..bad8a74 100644
--- a/luni/src/test/java/libcore/javax/crypto/CipherTest.java
+++ b/luni/src/test/java/libcore/javax/crypto/CipherTest.java
@@ -979,6 +979,76 @@
         }
     }
 
+    public void testCipher_getInstance_CorrectPriority_AlgorithmOnlyFirst() throws Exception {
+        Provider mockProviderOnlyAlgorithm = new MockProvider("MockProviderOnlyAlgorithm") {
+            public void setup() {
+                put("Cipher.FOO", MockCipherSpi.AllKeyTypes.class.getName());
+            }
+        };
+        Provider mockProviderFullTransformSpecified = new MockProvider("MockProviderFull") {
+            public void setup() {
+                put("Cipher.FOO/FOO/FOO", MockCipherSpi.AllKeyTypes.class.getName());
+            }
+        };
+
+        Security.addProvider(mockProviderOnlyAlgorithm);
+        Security.addProvider(mockProviderFullTransformSpecified);
+        try {
+            Cipher c = Cipher.getInstance("FOO/FOO/FOO");
+            assertEquals(mockProviderOnlyAlgorithm, c.getProvider());
+        } finally {
+            Security.removeProvider(mockProviderOnlyAlgorithm.getName());
+            Security.removeProvider(mockProviderFullTransformSpecified.getName());
+        }
+    }
+
+    public void testCipher_getInstance_CorrectPriority_FullTransformFirst() throws Exception {
+        Provider mockProviderOnlyAlgorithm = new MockProvider("MockProviderOnlyAlgorithm") {
+            public void setup() {
+                put("Cipher.FOO", MockCipherSpi.AllKeyTypes.class.getName());
+            }
+        };
+        Provider mockProviderFullTransformSpecified = new MockProvider("MockProviderFull") {
+            public void setup() {
+                put("Cipher.FOO/FOO/FOO", MockCipherSpi.AllKeyTypes.class.getName());
+            }
+        };
+
+        Security.addProvider(mockProviderFullTransformSpecified);
+        Security.addProvider(mockProviderOnlyAlgorithm);
+        try {
+            Cipher c = Cipher.getInstance("FOO/FOO/FOO");
+            assertEquals(mockProviderFullTransformSpecified, c.getProvider());
+        } finally {
+            Security.removeProvider(mockProviderOnlyAlgorithm.getName());
+            Security.removeProvider(mockProviderFullTransformSpecified.getName());
+        }
+    }
+
+    public void testCipher_getInstance_CorrectPriority_AliasedAlgorithmFirst() throws Exception {
+        Provider mockProviderAliasedAlgorithm = new MockProvider("MockProviderAliasedAlgorithm") {
+            public void setup() {
+                put("Cipher.BAR", MockCipherSpi.AllKeyTypes.class.getName());
+                put("Alg.Alias.Cipher.FOO", "BAR");
+            }
+        };
+        Provider mockProviderAlgorithmOnly = new MockProvider("MockProviderAlgorithmOnly") {
+            public void setup() {
+                put("Cipher.FOO", MockCipherSpi.AllKeyTypes.class.getName());
+            }
+        };
+
+        Security.addProvider(mockProviderAliasedAlgorithm);
+        Security.addProvider(mockProviderAlgorithmOnly);
+        try {
+            Cipher c = Cipher.getInstance("FOO/FOO/FOO");
+            assertEquals(mockProviderAliasedAlgorithm, c.getProvider());
+        } finally {
+            Security.removeProvider(mockProviderAliasedAlgorithm.getName());
+            Security.removeProvider(mockProviderAlgorithmOnly.getName());
+        }
+    }
+
     public void testCipher_getInstance_WrongType_Failure() throws Exception {
         Provider mockProviderInvalid = new MockProvider("MockProviderInvalid") {
             public void setup() {