KeyChain: Start supporting user-selectability of keys.

This change makes the AliasLoader filter out aliases for keys that are
not user-selectable.

This is the beginning of the work to enable controlling access to
DPC-installed keys in KeyChain.

Bug: 65624467
Test: New unit tests.
Change-Id: Ie829bfdbd31c4738702bef661d00064c691143c7
diff --git a/robotests/src/com/android/keychain/AliasLoaderTest.java b/robotests/src/com/android/keychain/AliasLoaderTest.java
index 1c4c50c..f2a05c8 100644
--- a/robotests/src/com/android/keychain/AliasLoaderTest.java
+++ b/robotests/src/com/android/keychain/AliasLoaderTest.java
@@ -21,6 +21,7 @@
 
 import android.security.Credentials;
 import android.security.KeyStore;
+import com.android.keychain.internal.KeyInfoProvider;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
@@ -37,9 +38,17 @@
 @RunWith(RobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
 public final class AliasLoaderTest {
+    private KeyInfoProvider mDummyInfoProvider;
 
     @Before
-    public void setUp() {}
+    public void setUp() {
+        mDummyInfoProvider =
+                new KeyInfoProvider() {
+                    public boolean isUserSelectable(String alias) {
+                        return true;
+                    }
+                };
+    }
 
     @Test
     public void testAliasLoader_loadsAllAliases()
@@ -49,7 +58,8 @@
         when(keyStore.list(Credentials.USER_PRIVATE_KEY)).thenReturn(new String[] {"b", "c", "a"});
 
         KeyChainActivity.AliasLoader loader =
-                new KeyChainActivity.AliasLoader(keyStore, RuntimeEnvironment.application);
+                new KeyChainActivity.AliasLoader(
+                        keyStore, RuntimeEnvironment.application, mDummyInfoProvider);
         loader.execute();
 
         ShadowApplication.runBackgroundTasks();
@@ -60,4 +70,45 @@
         Assert.assertEquals("b", result.getItem(1));
         Assert.assertEquals("c", result.getItem(2));
     }
+
+    @Test
+    public void testAliasLoader_copesWithNoAliases()
+            throws InterruptedException, ExecutionException, CancellationException,
+                    TimeoutException {
+        KeyStore keyStore = mock(KeyStore.class);
+        when(keyStore.list(Credentials.USER_PRIVATE_KEY)).thenReturn(null);
+
+        KeyChainActivity.AliasLoader loader =
+                new KeyChainActivity.AliasLoader(
+                        keyStore, RuntimeEnvironment.application, mDummyInfoProvider);
+        loader.execute();
+
+        ShadowApplication.runBackgroundTasks();
+        KeyChainActivity.CertificateAdapter result = loader.get(5, TimeUnit.SECONDS);
+        Assert.assertNotNull(result);
+        Assert.assertEquals(0, result.getCount());
+    }
+
+    @Test
+    public void testAliasLoader_filtersNonUserSelectableAliases()
+            throws InterruptedException, ExecutionException, CancellationException,
+                    TimeoutException {
+        KeyStore keyStore = mock(KeyStore.class);
+        when(keyStore.list(Credentials.USER_PRIVATE_KEY)).thenReturn(new String[] {"a", "b", "c"});
+        KeyInfoProvider infoProvider = mock(KeyInfoProvider.class);
+        when(infoProvider.isUserSelectable("a")).thenReturn(false);
+        when(infoProvider.isUserSelectable("b")).thenReturn(true);
+        when(infoProvider.isUserSelectable("c")).thenReturn(false);
+
+        KeyChainActivity.AliasLoader loader =
+                new KeyChainActivity.AliasLoader(
+                        keyStore, RuntimeEnvironment.application, infoProvider);
+        loader.execute();
+
+        ShadowApplication.runBackgroundTasks();
+        KeyChainActivity.CertificateAdapter result = loader.get(5, TimeUnit.SECONDS);
+        Assert.assertNotNull(result);
+        Assert.assertEquals(1, result.getCount());
+        Assert.assertEquals("b", result.getItem(0));
+    }
 }
diff --git a/src/com/android/keychain/KeyChainActivity.java b/src/com/android/keychain/KeyChainActivity.java
index ed8cd8c..79ecc79 100644
--- a/src/com/android/keychain/KeyChainActivity.java
+++ b/src/com/android/keychain/KeyChainActivity.java
@@ -47,6 +47,7 @@
 import android.widget.RadioButton;
 import android.widget.TextView;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.keychain.internal.KeyInfoProvider;
 import com.android.org.bouncycastle.asn1.x509.X509Name;
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
@@ -58,6 +59,7 @@
 import java.util.Collections;
 import java.util.concurrent.ExecutionException;
 import java.util.List;
+import java.util.stream.Collectors;
 
 import javax.security.auth.x500.X500Principal;
 
@@ -145,7 +147,8 @@
     private void chooseCertificate() {
         // Start loading the set of certs to choose from now- if device policy doesn't return an
         // alias, having aliases loading already will save some time waiting for UI to start.
-        final AliasLoader loader = new AliasLoader(mKeyStore, this);
+        // TODO: Once KeyChainService supports this interface, replace with that instance.
+        final AliasLoader loader = new AliasLoader(mKeyStore, this, (alias) -> { return true; });
         loader.execute();
 
         final IKeyChainAliasCallback.Stub callback = new IKeyChainAliasCallback.Stub() {
@@ -197,18 +200,22 @@
     static class AliasLoader extends AsyncTask<Void, Void, CertificateAdapter> {
         private final KeyStore mKeyStore;
         private final Context mContext;
-        public AliasLoader(KeyStore keyStore, Context context) {
+        private final KeyInfoProvider mInfoProvider;
+
+        public AliasLoader(KeyStore keyStore, Context context, KeyInfoProvider infoProvider) {
           mKeyStore = keyStore;
           mContext = context;
+          mInfoProvider = infoProvider;
         }
 
         @Override protected CertificateAdapter doInBackground(Void... params) {
             String[] aliasArray = mKeyStore.list(Credentials.USER_PRIVATE_KEY);
-            List<String> aliasList = ((aliasArray == null)
+            List<String> rawAliasList = ((aliasArray == null)
                                       ? Collections.<String>emptyList()
                                       : Arrays.asList(aliasArray));
-            Collections.sort(aliasList);
-            return new CertificateAdapter(mKeyStore, mContext, aliasList);
+            return new CertificateAdapter(mKeyStore, mContext,
+                    rawAliasList.stream().filter(mInfoProvider::isUserSelectable).sorted()
+                    .collect(Collectors.toList()));
         }
     }
 
diff --git a/src/com/android/keychain/internal/KeyInfoProvider.java b/src/com/android/keychain/internal/KeyInfoProvider.java
new file mode 100644
index 0000000..c5d1e30
--- /dev/null
+++ b/src/com/android/keychain/internal/KeyInfoProvider.java
@@ -0,0 +1,14 @@
+package com.android.keychain.internal;
+
+/** Interface for classes that provide information about keys in KeyChain.  */
+
+public interface KeyInfoProvider {
+    /**
+     * Indicates whether a key associated with the given alias is allowed
+     * to be selected by users.
+     * @param alias Alias of the key to check.
+     *
+     * @return true if the provided alias is selectable by users.
+     */
+    public boolean isUserSelectable(String alias);
+}