Cipher: always select a SPI in init according to the parameters

Behaviour before this CL was to avoid selecting a SPI if there
was a previously selected one. That is an incompatibility wrt M.

This CL makes Cipher compatible with the M behaviour

(cherry picked from commit 264d21f1e86e7ec7976fc5346c21a8e17d8635b1)

Bug: 29038928
Change-Id: Iad68ec2c1eca99c4e98f5179429498cff9b42c36
diff --git a/luni/src/test/java/libcore/javax/crypto/CipherTest.java b/luni/src/test/java/libcore/javax/crypto/CipherTest.java
index 13c9ceb..cc5376a 100644
--- a/luni/src/test/java/libcore/javax/crypto/CipherTest.java
+++ b/luni/src/test/java/libcore/javax/crypto/CipherTest.java
@@ -3902,4 +3902,111 @@
         }
     }
 
+    /**
+     * http://b/29038928
+     * If in a second call to init the current spi doesn't support the new specified key, look for
+     * another suitable spi.
+     */
+    public void test_init_onKeyTypeChange_reInitCipher() throws Exception {
+        Provider mockProvider = new MockProvider("MockProvider") {
+            public void setup() {
+                put("Cipher.FOO", MockCipherSpi.SpecificKeyTypes.class.getName());
+            }
+        };
+        Provider mockProvider2 = new MockProvider("MockProvider2") {
+            public void setup() {
+                put("Cipher.FOO", MockCipherSpi.SpecificKeyTypes2.class.getName());
+            }
+        };
+        try {
+            Security.addProvider(mockProvider);
+            Security.addProvider(mockProvider2);
+            Cipher cipher = Cipher.getInstance("FOO");
+            cipher.init(Cipher.ENCRYPT_MODE, new MockKey());
+            assertEquals("MockProvider", cipher.getProvider().getName());
+            // Using a different key...
+            cipher.init(Cipher.ENCRYPT_MODE, new MockKey2());
+            // ...results in a different provider.
+            assertEquals("MockProvider2", cipher.getProvider().getName());
+        } finally {
+            Security.removeProvider(mockProvider.getName());
+            Security.removeProvider(mockProvider2.getName());
+        }
+    }
+
+    /**
+     * http://b/29038928
+     * If in a second call to init the current spi doesn't support the new specified
+     * {@link AlgorithmParameterSpec}, look for another suitable spi.
+     */
+    public void test_init_onAlgorithmParameterTypeChange_reInitCipher() throws Exception {
+        Provider mockProvider = new MockProvider("MockProvider") {
+            public void setup() {
+                put("Cipher.FOO",
+                        MockCipherSpi.SpecificAlgorithmParameterSpecTypes.class.getName());
+            }
+        };
+        Provider mockProvider2 = new MockProvider("MockProvider2") {
+            public void setup() {
+                put("Cipher.FOO",
+                        MockCipherSpi.SpecificAlgorithmParameterSpecTypes2.class.getName());
+            }
+        };
+        try {
+            Security.addProvider(mockProvider);
+            Security.addProvider(mockProvider2);
+            Cipher cipher = Cipher.getInstance("FOO");
+            cipher.init(Cipher.ENCRYPT_MODE,
+                    new MockKey(),
+                    new MockCipherSpi.MockAlgorithmParameterSpec());
+            assertEquals("MockProvider", cipher.getProvider().getName());
+            // Using a different AlgorithmParameterSpec...
+            cipher.init(Cipher.ENCRYPT_MODE,
+                    new MockKey(),
+                    new MockCipherSpi.MockAlgorithmParameterSpec2());
+            // ...results in a different provider.
+            assertEquals("MockProvider2", cipher.getProvider().getName());
+        } finally {
+            Security.removeProvider(mockProvider.getName());
+            Security.removeProvider(mockProvider2.getName());
+        }
+    }
+
+    /**
+     * http://b/29038928
+     * If in a second call to init the current spi doesn't support the new specified
+     * {@link AlgorithmParameters}, look for another suitable spi.
+     */
+    public void test_init_onAlgorithmParametersChange_reInitCipher() throws Exception {
+        Provider mockProvider = new MockProvider("MockProvider") {
+            public void setup() {
+                put("Cipher.FOO",
+                        MockCipherSpi.SpecificAlgorithmParameterAesAlgorithm.class.getName());
+            }
+        };
+        Provider mockProvider2 = new MockProvider("MockProvider2") {
+            public void setup() {
+                put("Cipher.FOO",
+                        MockCipherSpi.SpecificAlgorithmParametersDesAlgorithm.class.getName());
+            }
+        };
+        try {
+            Security.addProvider(mockProvider);
+            Security.addProvider(mockProvider2);
+            Cipher cipher = Cipher.getInstance("FOO");
+            cipher.init(Cipher.ENCRYPT_MODE,
+                    new MockKey(),
+                    AlgorithmParameters.getInstance("AES"));
+            assertEquals("MockProvider", cipher.getProvider().getName());
+            // Using a different AlgorithmParameters...
+            cipher.init(Cipher.ENCRYPT_MODE,
+                    new MockKey(),
+                    AlgorithmParameters.getInstance("DES"));
+            // ...results in a different provider.
+            assertEquals("MockProvider2", cipher.getProvider().getName());
+        } finally {
+            Security.removeProvider(mockProvider.getName());
+            Security.removeProvider(mockProvider2.getName());
+        }
+    }
 }
diff --git a/luni/src/test/java/libcore/javax/crypto/MockCipherSpi.java b/luni/src/test/java/libcore/javax/crypto/MockCipherSpi.java
index c1b1bd2..0fd2aaa 100644
--- a/luni/src/test/java/libcore/javax/crypto/MockCipherSpi.java
+++ b/luni/src/test/java/libcore/javax/crypto/MockCipherSpi.java
@@ -54,6 +54,48 @@
         }
     }
 
+    public static class SpecificAlgorithmParameterSpecTypes extends MockCipherSpi {
+        @Override
+        public void checkAlgorithmParameterSpec(AlgorithmParameterSpec aps)
+                throws InvalidAlgorithmParameterException {
+            if (!(aps instanceof MockAlgorithmParameterSpec)) {
+                throw new InvalidAlgorithmParameterException("Must be "
+                        + MockAlgorithmParameterSpec.class.getName());
+            }
+        }
+    }
+
+    public static class SpecificAlgorithmParameterSpecTypes2 extends MockCipherSpi {
+        @Override
+        public void checkAlgorithmParameterSpec(AlgorithmParameterSpec aps)
+                throws InvalidAlgorithmParameterException {
+            if (!(aps instanceof MockAlgorithmParameterSpec2)) {
+                throw new InvalidAlgorithmParameterException("Must be "
+                        + MockAlgorithmParameterSpec2.class.getName());
+            }
+        }
+    }
+
+    public static class SpecificAlgorithmParameterAesAlgorithm extends MockCipherSpi {
+        @Override
+        public void checkAlgorithmParameters(AlgorithmParameters ap)
+                throws InvalidAlgorithmParameterException {
+            if (!ap.getAlgorithm().equals("AES")) {
+                throw new InvalidAlgorithmParameterException("Must be AES");
+            }
+        }
+    }
+
+    public static class SpecificAlgorithmParametersDesAlgorithm extends MockCipherSpi {
+        @Override
+        public void checkAlgorithmParameters(AlgorithmParameters ap)
+                throws InvalidAlgorithmParameterException {
+            if ((!ap.getAlgorithm().equals("DES"))) {
+                throw new InvalidAlgorithmParameterException("Must be DES");
+            }
+        }
+    }
+
     public static class AllKeyTypes extends MockCipherSpi {
     }
 
@@ -146,6 +188,15 @@
     public void checkKeyType(Key key) throws InvalidKeyException {
     }
 
+    public void checkAlgorithmParameterSpec(AlgorithmParameterSpec aps)
+            throws InvalidAlgorithmParameterException {
+    }
+
+    public void checkAlgorithmParameters(AlgorithmParameters ap)
+            throws InvalidAlgorithmParameterException {
+    }
+
+
     @Override
     protected void engineSetMode(String mode) throws NoSuchAlgorithmException {
         if (!"FOO".equals(mode)) {
@@ -189,12 +240,14 @@
     protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params,
             SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
         checkKeyType(key);
+        checkAlgorithmParameterSpec(params);
     }
 
     @Override
     protected void engineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random)
             throws InvalidKeyException, InvalidAlgorithmParameterException {
         checkKeyType(key);
+        checkAlgorithmParameters(params);
     }
 
     @Override
@@ -220,4 +273,10 @@
             BadPaddingException {
         throw new UnsupportedOperationException("not implemented");
     }
+
+    public static class MockAlgorithmParameterSpec implements AlgorithmParameterSpec {
+    }
+
+    public static class MockAlgorithmParameterSpec2 implements AlgorithmParameterSpec {
+    }
 }
diff --git a/ojluni/src/main/java/javax/crypto/Cipher.java b/ojluni/src/main/java/javax/crypto/Cipher.java
index 39dd524..2d9c207 100755
--- a/ojluni/src/main/java/javax/crypto/Cipher.java
+++ b/ojluni/src/main/java/javax/crypto/Cipher.java
@@ -1195,15 +1195,11 @@
         initialized = false;
         checkOpmode(opmode);
 
-        if (spi != null && (key == null)) {
-            spi.engineInit(opmode, key, random);
-        } else {
-            try {
-                chooseProvider(InitType.KEY, opmode, key, null, null, random);
-            } catch (InvalidAlgorithmParameterException e) {
-                // should never occur
-                throw new InvalidKeyException(e);
-            }
+        try {
+            chooseProvider(InitType.KEY, opmode, key, null, null, random);
+        } catch (InvalidAlgorithmParameterException e) {
+            // should never occur
+            throw new InvalidKeyException(e);
         }
 
         initialized = true;
@@ -1330,11 +1326,7 @@
         initialized = false;
         checkOpmode(opmode);
 
-        if (spi != null) {
-            spi.engineInit(opmode, key, params, random);
-        } else {
-            chooseProvider(InitType.ALGORITHM_PARAM_SPEC, opmode, key, params, null, random);
-        }
+        chooseProvider(InitType.ALGORITHM_PARAM_SPEC, opmode, key, params, null, random);
 
         initialized = true;
         this.opmode = opmode;
@@ -1460,11 +1452,7 @@
         initialized = false;
         checkOpmode(opmode);
 
-        if (spi != null) {
-            spi.engineInit(opmode, key, params, random);
-        } else {
-            chooseProvider(InitType.ALGORITHM_PARAMS, opmode, key, null, params, random);
-        }
+        chooseProvider(InitType.ALGORITHM_PARAMS, opmode, key, null, params, random);
 
         initialized = true;
         this.opmode = opmode;
@@ -1599,8 +1587,7 @@
      */
     public final void init(int opmode, Certificate certificate,
                            SecureRandom random)
-            throws InvalidKeyException
-    {
+            throws InvalidKeyException {
         initialized = false;
         checkOpmode(opmode);
 
@@ -1609,38 +1596,34 @@
         if (certificate instanceof java.security.cert.X509Certificate) {
             // Check whether the cert has a key usage extension
             // marked as a critical extension.
-            X509Certificate cert = (X509Certificate)certificate;
+            X509Certificate cert = (X509Certificate) certificate;
             Set critSet = cert.getCriticalExtensionOIDs();
 
             if (critSet != null && !critSet.isEmpty()
-                && critSet.contains(KEY_USAGE_EXTENSION_OID)) {
+                    && critSet.contains(KEY_USAGE_EXTENSION_OID)) {
                 boolean[] keyUsageInfo = cert.getKeyUsage();
                 // keyUsageInfo[2] is for keyEncipherment;
                 // keyUsageInfo[3] is for dataEncipherment.
                 if ((keyUsageInfo != null) &&
-                    (((opmode == Cipher.ENCRYPT_MODE) &&
-                      (keyUsageInfo.length > 3) &&
-                      (keyUsageInfo[3] == false)) ||
-                     ((opmode == Cipher.WRAP_MODE) &&
-                      (keyUsageInfo.length > 2) &&
-                      (keyUsageInfo[2] == false)))) {
+                        (((opmode == Cipher.ENCRYPT_MODE) &&
+                                (keyUsageInfo.length > 3) &&
+                                (keyUsageInfo[3] == false)) ||
+                                ((opmode == Cipher.WRAP_MODE) &&
+                                        (keyUsageInfo.length > 2) &&
+                                        (keyUsageInfo[2] == false)))) {
                     throw new InvalidKeyException("Wrong key usage");
                 }
             }
         }
 
         PublicKey publicKey =
-            (certificate==null? null:certificate.getPublicKey());
+                (certificate == null ? null : certificate.getPublicKey());
 
-        if (spi != null) {
-            spi.engineInit(opmode, publicKey, random);
-        } else {
-            try {
-                chooseProvider(InitType.KEY, opmode, (Key)publicKey, null, null, random);
-            } catch (InvalidAlgorithmParameterException e) {
-                // should never occur
-                throw new InvalidKeyException(e);
-            }
+        try {
+            chooseProvider(InitType.KEY, opmode, (Key) publicKey, null, null, random);
+        } catch (InvalidAlgorithmParameterException e) {
+            // should never occur
+            throw new InvalidKeyException(e);
         }
 
         initialized = true;