Merge "Late binding: Provider.Service#supportsParameter"
diff --git a/luni/src/main/java/java/security/Provider.java b/luni/src/main/java/java/security/Provider.java
index a6de19c..c93d4dd 100644
--- a/luni/src/main/java/java/security/Provider.java
+++ b/luni/src/main/java/java/security/Provider.java
@@ -804,6 +804,40 @@
      * provider it belongs and other properties.
      */
     public static class Service {
+        /** Attribute name of supported key classes. */
+        private static final String ATTR_SUPPORTED_KEY_CLASSES = "SupportedKeyClasses";
+
+        /** Attribute name of supported key formats. */
+        private static final String ATTR_SUPPORTED_KEY_FORMATS = "SupportedKeyFormats";
+
+        private static final HashMap<String, Boolean> supportsParameterTypes
+                = new HashMap<String, Boolean>();
+        static {
+            // Does not support parameter
+            supportsParameterTypes.put("AlgorithmParameterGenerator", false);
+            supportsParameterTypes.put("AlgorithmParameters", false);
+            supportsParameterTypes.put("CertificateFactory", false);
+            supportsParameterTypes.put("CertPathBuilder", false);
+            supportsParameterTypes.put("CertPathValidator", false);
+            supportsParameterTypes.put("CertStore", false);
+            supportsParameterTypes.put("KeyFactory", false);
+            supportsParameterTypes.put("KeyGenerator", false);
+            supportsParameterTypes.put("KeyManagerFactory", false);
+            supportsParameterTypes.put("KeyPairGenerator", false);
+            supportsParameterTypes.put("KeyStore", false);
+            supportsParameterTypes.put("MessageDigest", false);
+            supportsParameterTypes.put("SecretKeyFactory", false);
+            supportsParameterTypes.put("SecureRandom", false);
+            supportsParameterTypes.put("SSLContext", false);
+            supportsParameterTypes.put("TrustManagerFactory", false);
+
+            // Supports parameter
+            supportsParameterTypes.put("Cipher", true);
+            supportsParameterTypes.put("KeyAgreement", true);
+            supportsParameterTypes.put("Mac", true);
+            supportsParameterTypes.put("Signature", true);
+        }
+
         // The provider
         private Provider provider;
 
@@ -828,6 +862,15 @@
         // For newInstance() optimization
         private String lastClassName;
 
+        /** Indicates whether supportedKeyClasses and supportedKeyFormats. */
+        private volatile boolean supportedKeysInitialized;
+
+        /** List of classes that this service supports. */
+        private Class<?>[] keyClasses;
+
+        /** List of key formats this service supports. */
+        private String[] keyFormats;
+
         /**
          * Constructs a new instance of {@code Service} with the given
          * attributes.
@@ -1005,7 +1048,7 @@
                 throw new InvalidParameterException(type + ": service cannot use the parameter");
             }
 
-            Class[] parameterTypes = new Class[1];
+            Class<?>[] parameterTypes = new Class<?>[1];
             Object[] initargs = { constructorParameter };
             try {
                 if (type.equalsIgnoreCase("CertStore")) {
@@ -1031,7 +1074,89 @@
          *         constructor parameter, {@code false} otherwise.
          */
         public boolean supportsParameter(Object parameter) {
-            return true;
+            Boolean supportsParameter = supportsParameterTypes.get(type);
+            if (supportsParameter == null) {
+                return true;
+            }
+            if (!supportsParameter) {
+                throw new InvalidParameterException("Cannot use a parameter with " + type);
+            }
+
+            /*
+             * Only key type parameters are allowed, but allow null since there
+             * might not be any listed classes or formats for this instance.
+             */
+            if (parameter != null && !(parameter instanceof Key)) {
+                throw new InvalidParameterException("Parameter should be of type Key");
+            }
+
+            ensureSupportedKeysInitialized();
+
+            // No restriction specified by Provider registration.
+            if (keyClasses == null && keyFormats == null) {
+                return true;
+            }
+
+            Key keyParam = (Key) parameter;
+            if (keyClasses != null && isInArray(keyClasses, keyParam.getClass())) {
+                return true;
+            }
+            if (keyFormats != null && isInArray(keyFormats, keyParam.getFormat())) {
+                return true;
+            }
+
+            return false;
+        }
+
+        /**
+         * Initialize the list of supported key classes and formats.
+         */
+        private void ensureSupportedKeysInitialized() {
+            if (supportedKeysInitialized) {
+                return;
+            }
+
+            final String supportedClassesString = getAttribute(ATTR_SUPPORTED_KEY_CLASSES);
+            if (supportedClassesString != null) {
+                String[] keyClassNames = supportedClassesString.split("\\|");
+                ArrayList<Class<?>> supportedClassList = new ArrayList<Class<?>>(
+                        keyClassNames.length);
+                final ClassLoader classLoader = getProvider().getClass().getClassLoader();
+                for (String keyClassName : keyClassNames) {
+                    try {
+                        Class<?> keyClass = classLoader.loadClass(keyClassName);
+                        if (Key.class.isAssignableFrom(keyClass)) {
+                            supportedClassList.add(keyClass);
+                        }
+                    } catch (ClassNotFoundException ignored) {
+                    }
+                }
+                keyClasses = supportedClassList.toArray(new Class<?>[supportedClassList.size()]);
+            }
+
+            final String supportedFormatString = getAttribute(ATTR_SUPPORTED_KEY_FORMATS);
+            if (supportedFormatString != null) {
+                keyFormats = supportedFormatString.split("\\|");
+            }
+
+            supportedKeysInitialized = true;
+        }
+
+        /**
+         * Check if an item is in the array. The array of supported key classes
+         * and formats is usually just a length of 1, so a simple array is
+         * faster than a Set.
+         */
+        private static final <T> boolean isInArray(T[] itemList, T target) {
+            if (target == null) {
+                return false;
+            }
+            for (T item : itemList) {
+                if (target.equals(item)) {
+                    return true;
+                }
+            }
+            return false;
         }
 
         /**
diff --git a/luni/src/test/java/libcore/java/security/ProviderTest.java b/luni/src/test/java/libcore/java/security/ProviderTest.java
index 8c91bc1..248d3ff 100644
--- a/luni/src/test/java/libcore/java/security/ProviderTest.java
+++ b/luni/src/test/java/libcore/java/security/ProviderTest.java
@@ -16,12 +16,15 @@
 
 package libcore.java.security;
 
+import java.security.InvalidParameterException;
 import java.security.NoSuchAlgorithmException;
 import java.security.Provider;
 import java.security.SecureRandom;
 import java.security.SecureRandomSpi;
 import java.security.Security;
+import java.security.interfaces.RSAPrivateKey;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -34,6 +37,7 @@
 import javax.crypto.Cipher;
 import javax.crypto.NoSuchPaddingException;
 import junit.framework.TestCase;
+import libcore.javax.crypto.MockKey;
 
 public class ProviderTest extends TestCase {
     private static final boolean LOG_DEBUG = false;
@@ -220,11 +224,291 @@
         }
     }
 
+    private static final String[] TYPES_SERVICES_CHECKED = new String[] {
+            "KeyFactory", "CertPathBuilder", "Cipher", "SecureRandom",
+            "AlgorithmParameterGenerator", "Signature", "KeyPairGenerator", "CertificateFactory",
+            "MessageDigest", "KeyAgreement", "CertStore", "SSLContext", "AlgorithmParameters",
+            "TrustManagerFactory", "KeyGenerator", "Mac", "CertPathValidator", "SecretKeyFactory",
+            "KeyManagerFactory", "KeyStore",
+    };
+
+    private static final HashSet<String> TYPES_SUPPORTS_PARAMETER = new HashSet<String>(
+            Arrays.asList(new String[] {
+                    "Mac", "KeyAgreement", "Cipher", "Signature",
+            }));
+
+    private static final HashSet<String> TYPES_NOT_SUPPORTS_PARAMETER = new HashSet<String>(
+            Arrays.asList(TYPES_SERVICES_CHECKED));
+    static {
+        TYPES_NOT_SUPPORTS_PARAMETER.removeAll(TYPES_SUPPORTS_PARAMETER);
+    }
+
+    public void test_Provider_getServices_supportsParameter() throws Exception {
+        HashSet<String> remainingTypes = new HashSet<String>(Arrays.asList(TYPES_SERVICES_CHECKED));
+
+        HashSet<String> supportsParameterTypes = new HashSet<String>();
+        HashSet<String> noSupportsParameterTypes = new HashSet<String>();
+
+        Provider[] providers = Security.getProviders();
+        for (Provider provider : providers) {
+            Set<Provider.Service> services = provider.getServices();
+            assertNotNull(services);
+            assertFalse(services.isEmpty());
+
+            for (Provider.Service service : services) {
+                final String type = service.getType();
+                remainingTypes.remove(type);
+                try {
+                    service.supportsParameter(new MockKey());
+                    supportsParameterTypes.add(type);
+                } catch (InvalidParameterException e) {
+                    noSupportsParameterTypes.add(type);
+                    try {
+                        service.supportsParameter(new Object());
+                        fail("Should throw on non-Key parameter");
+                    } catch (InvalidParameterException expected) {
+                    }
+                }
+            }
+        }
+
+        supportsParameterTypes.retainAll(TYPES_SUPPORTS_PARAMETER);
+        assertEquals("Types that should support parameters", TYPES_SUPPORTS_PARAMETER,
+                supportsParameterTypes);
+
+        noSupportsParameterTypes.retainAll(TYPES_NOT_SUPPORTS_PARAMETER);
+        assertEquals("Types that should not support parameters", TYPES_NOT_SUPPORTS_PARAMETER,
+                noSupportsParameterTypes);
+
+        assertEquals("Types that should be checked", Collections.EMPTY_SET, remainingTypes);
+    }
+
+    public static class MockSpi {
+        public Object parameter;
+
+        public MockSpi(MockKey parameter) {
+            this.parameter = parameter;
+        }
+    };
+
+    @SuppressWarnings("serial")
+    public void testProviderService_supportsParameter_UnknownService_Success() throws Exception {
+        Provider provider = new MockProvider("MockProvider") {
+            public void setup() {
+                put("Fake.FOO", MockSpi.class.getName());
+            }
+        };
+
+        Security.addProvider(provider);
+        try {
+            Provider.Service service = provider.getService("Fake", "FOO");
+            assertTrue(service.supportsParameter(new Object()));
+        } finally {
+            Security.removeProvider(provider.getName());
+        }
+    }
+
+    @SuppressWarnings("serial")
+    public void testProviderService_supportsParameter_KnownService_NoClassInitialization_Success()
+            throws Exception {
+        Provider provider = new MockProvider("MockProvider") {
+            public void setup() {
+                put("Signature.FOO", MockSpi.class.getName());
+                put("Signature.FOO SupportedKeyClasses", getClass().getName()
+                        + ".UninitializedMockKey");
+            }
+        };
+
+        Security.addProvider(provider);
+        try {
+            Provider.Service service = provider.getService("Signature", "FOO");
+            assertFalse(service.supportsParameter(new MockKey()));
+        } finally {
+            Security.removeProvider(provider.getName());
+        }
+    }
+
+    @SuppressWarnings("serial")
+    public static class UninitializedMockKey extends MockKey {
+        static {
+            fail("This should not be initialized");
+        }
+    }
+
+    @SuppressWarnings("serial")
+    public void testProviderService_supportsParameter_TypeDoesNotSupportParameter_Failure()
+            throws Exception {
+        Provider provider = new MockProvider("MockProvider") {
+            public void setup() {
+                put("KeyFactory.FOO", MockSpi.class.getName());
+            }
+        };
+
+        Security.addProvider(provider);
+        try {
+            Provider.Service service = provider.getService("KeyFactory", "FOO");
+            try {
+                service.supportsParameter(new MockKey());
+                fail("Should always throw exception");
+            } catch (InvalidParameterException expected) {
+            }
+        } finally {
+            Security.removeProvider(provider.getName());
+        }
+    }
+
+    @SuppressWarnings("serial")
+    public void testProviderService_supportsParameter_SupportedKeyClasses_NonKeyClass_Success()
+            throws Exception {
+        Provider provider = new MockProvider("MockProvider") {
+            public void setup() {
+                put("Signature.FOO", MockSpi.class.getName());
+                put("Signature.FOO SupportedKeyClasses", MockSpi.class.getName());
+            }
+        };
+
+        Security.addProvider(provider);
+        try {
+            Provider.Service service = provider.getService("Signature", "FOO");
+            assertFalse(service.supportsParameter(new MockKey()));
+        } finally {
+            Security.removeProvider(provider.getName());
+        }
+    }
+
+    @SuppressWarnings("serial")
+    public void testProviderService_supportsParameter_KnownService_NonKey_Failure()
+            throws Exception {
+        Provider provider = new MockProvider("MockProvider") {
+            public void setup() {
+                put("Signature.FOO", MockSpi.class.getName());
+            }
+        };
+
+        Security.addProvider(provider);
+        try {
+            Provider.Service service = provider.getService("Signature", "FOO");
+            try {
+                service.supportsParameter(new Object());
+                fail("Should throw when non-Key passed in");
+            } catch (InvalidParameterException expected) {
+            }
+        } finally {
+            Security.removeProvider(provider.getName());
+        }
+    }
+
+    @SuppressWarnings("serial")
+    public void testProviderService_supportsParameter_KnownService_SupportedKeyClasses_NonKey_Failure()
+            throws Exception {
+        Provider provider = new MockProvider("MockProvider") {
+            public void setup() {
+                put("Signature.FOO", MockSpi.class.getName());
+                put("Signature.FOO SupportedKeyClasses", RSAPrivateKey.class.getName());
+            }
+        };
+
+        Security.addProvider(provider);
+        try {
+            Provider.Service service = provider.getService("Signature", "FOO");
+            try {
+                service.supportsParameter(new Object());
+                fail("Should throw on non-Key instance passed in");
+            } catch (InvalidParameterException expected) {
+            }
+        } finally {
+            Security.removeProvider(provider.getName());
+        }
+    }
+
+    @SuppressWarnings("serial")
+    public void testProviderService_supportsParameter_SupportedKeyClasses_Success()
+            throws Exception {
+        Provider provider = new MockProvider("MockProvider") {
+            public void setup() {
+                put("Signature.FOO", MockSpi.class.getName());
+                put("Signature.FOO SupportedKeyClasses", MockKey.class.getName());
+            }
+        };
+
+        Security.addProvider(provider);
+        try {
+            Provider.Service service = provider.getService("Signature", "FOO");
+            assertTrue(service.supportsParameter(new MockKey()));
+        } finally {
+            Security.removeProvider(provider.getName());
+        }
+    }
+
+    @SuppressWarnings("serial")
+    public void testProviderService_supportsParameter_SupportedKeyClasses_Failure()
+            throws Exception {
+        Provider provider = new MockProvider("MockProvider") {
+            public void setup() {
+                put("Signature.FOO", MockSpi.class.getName());
+                put("Signature.FOO SupportedKeyClasses", RSAPrivateKey.class.getName());
+            }
+        };
+
+        Security.addProvider(provider);
+        try {
+            Provider.Service service = provider.getService("Signature", "FOO");
+            assertFalse(service.supportsParameter(new MockKey()));
+        } finally {
+            Security.removeProvider(provider.getName());
+        }
+    }
+
+    @SuppressWarnings("serial")
+    public void testProviderService_supportsParameter_SupportedKeyFormats_Success()
+            throws Exception {
+        Provider provider = new MockProvider("MockProvider") {
+            public void setup() {
+                put("Signature.FOO", MockSpi.class.getName());
+                put("Signature.FOO SupportedKeyFormats", new MockKey().getFormat());
+            }
+        };
+
+        Security.addProvider(provider);
+        try {
+            Provider.Service service = provider.getService("Signature", "FOO");
+            assertTrue(service.supportsParameter(new MockKey()));
+        } finally {
+            Security.removeProvider(provider.getName());
+        }
+    }
+
+    @SuppressWarnings("serial")
+    public void testProviderService_supportsParameter_SupportedKeyFormats_Failure()
+            throws Exception {
+        Provider provider = new MockProvider("MockProvider") {
+            public void setup() {
+                put("Signature.FOO", MockSpi.class.getName());
+                put("Signature.FOO SupportedKeyFormats", "Invalid");
+            }
+        };
+
+        Security.addProvider(provider);
+        try {
+            Provider.Service service = provider.getService("Signature", "FOO");
+            assertFalse(service.supportsParameter(new MockKey()));
+        } finally {
+            Security.removeProvider(provider.getName());
+        }
+    }
+
     /**
      * http://code.google.com/p/android/issues/detail?id=21449
      */
     public void testSecureRandomImplementationOrder() {
-        Provider srp = new SRProvider();
+        @SuppressWarnings("serial")
+        Provider srp = new MockProvider("SRProvider") {
+            public void setup() {
+                put("SecureRandom.SecureRandom1", SecureRandom1.class.getName());
+                put("SecureRandom.SecureRandom2", SecureRandom2.class.getName());
+                put("SecureRandom.SecureRandom3", SecureRandom3.class.getName());
+            }
+        };
         try {
             int position = Security.insertProviderAt(srp, 1); // first is one, not zero
             assertEquals(1, position);
@@ -237,16 +521,17 @@
         }
     }
 
-    public static class SRProvider extends Provider {
-
-        SRProvider() {
-            super("SRProvider", 1.42, "SecureRandom Provider");
-            put("SecureRandom.SecureRandom1", SecureRandom1.class.getName());
-            put("SecureRandom.SecureRandom2", SecureRandom2.class.getName());
-            put("SecureRandom.SecureRandom3", SecureRandom3.class.getName());
+    @SuppressWarnings("serial")
+    private static abstract class MockProvider extends Provider {
+        public MockProvider(String name) {
+            super(name, 1.0, "Mock provider used for testing");
+            setup();
         }
+
+        public abstract void setup();
     }
 
+    @SuppressWarnings("serial")
     public static abstract class AbstractSecureRandom extends SecureRandomSpi {
         protected void engineSetSeed(byte[] seed) {
             throw new UnsupportedOperationException();
@@ -259,8 +544,13 @@
         }
     }
 
+    @SuppressWarnings("serial")
     public static class SecureRandom1 extends AbstractSecureRandom {}
+
+    @SuppressWarnings("serial")
     public static class SecureRandom2 extends AbstractSecureRandom {}
+
+    @SuppressWarnings("serial")
     public static class SecureRandom3 extends AbstractSecureRandom {}
 
 }
diff --git a/luni/src/test/java/libcore/javax/crypto/MockKey.java b/luni/src/test/java/libcore/javax/crypto/MockKey.java
new file mode 100644
index 0000000..248e2de
--- /dev/null
+++ b/luni/src/test/java/libcore/javax/crypto/MockKey.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 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.javax.crypto;
+
+import java.security.Key;
+
+/**
+ * A mock Key class used for testing.
+ */
+@SuppressWarnings("serial")
+public class MockKey implements Key {
+    @Override
+    public String getAlgorithm() {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    public String getFormat() {
+        return "MOCK";
+    }
+
+    @Override
+    public byte[] getEncoded() {
+        throw new UnsupportedOperationException("not implemented");
+    }
+}