Use device policy suggestions when choosing alias

Query device policy for a suggested certificate before showing any
kind of selection UI. The RPC call is made at the same time as
retrieving and listing certificates so performance impact should be
relatively small.

Bug: 15065444
Change-Id: I0729b435c218b7991e6cb5faedefb7900577afcc
diff --git a/src/com/android/keychain/KeyChainActivity.java b/src/com/android/keychain/KeyChainActivity.java
index 23aa1a1..6b876c4 100644
--- a/src/com/android/keychain/KeyChainActivity.java
+++ b/src/com/android/keychain/KeyChainActivity.java
@@ -17,15 +17,20 @@
 package com.android.keychain;
 
 import android.app.Activity;
+import android.app.admin.IDevicePolicyManager;
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.PendingIntent;
+import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.os.AsyncTask;
 import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.security.Credentials;
 import android.security.IKeyChainAliasCallback;
 import android.security.KeyChain;
@@ -49,6 +54,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.concurrent.ExecutionException;
 import java.util.List;
 
 import javax.security.auth.x500.X500Principal;
@@ -118,7 +124,7 @@
                     // onActivityResult is called with REQUEST_UNLOCK
                     return;
                 }
-                showCertChooserDialog();
+                chooseCertificate();
                 return;
             case UNLOCK_REQUESTED:
                 // we've already asked, but have not heard back, probably just rotated.
@@ -134,8 +140,59 @@
         }
     }
 
-    private void showCertChooserDialog() {
-        new AliasLoader().execute();
+    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();
+        loader.execute();
+
+        final IKeyChainAliasCallback.Stub callback = new IKeyChainAliasCallback.Stub() {
+            @Override public void alias(String alias) {
+                // Use policy-suggested alias if provided
+                if (alias != null) {
+                    finish(alias);
+                    return;
+                }
+
+                // No suggested alias - instead finish loading and show UI to pick one
+                final CertificateAdapter certAdapter;
+                try {
+                    certAdapter = loader.get();
+                } catch (InterruptedException | ExecutionException e) {
+                    Log.e(TAG, "Loading certificate aliases interrupted", e);
+                    finish(null);
+                    return;
+                }
+                runOnUiThread(new Runnable() {
+                    @Override public void run() {
+                        displayCertChooserDialog(certAdapter);
+                    }
+                });
+            }
+        };
+
+        // Give a profile or device owner the chance to intercept the request, if a private key
+        // access listener is registered with the DevicePolicyManagerService.
+        IDevicePolicyManager devicePolicyManager = IDevicePolicyManager.Stub.asInterface(
+                ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
+
+        String host = getIntent().getStringExtra(KeyChain.EXTRA_HOST);
+        int port = getIntent().getIntExtra(KeyChain.EXTRA_PORT, -1);
+        String url = getIntent().getStringExtra(KeyChain.EXTRA_URL);
+        String alias = getIntent().getStringExtra(KeyChain.EXTRA_ALIAS);
+
+        try {
+            devicePolicyManager.choosePrivateKeyAlias(host, port, url, alias, callback);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Could not bind to DevicePolicyManagerService", e);
+
+            // Proceed without a suggested alias.
+            try {
+                callback.alias(null);
+            } catch (RemoteException shouldNeverHappen) {
+                finish(null);
+            }
+        }
     }
 
     private class AliasLoader extends AsyncTask<Void, Void, CertificateAdapter> {
@@ -147,9 +204,6 @@
             Collections.sort(aliasList);
             return new CertificateAdapter(aliasList);
         }
-        @Override protected void onPostExecute(CertificateAdapter adapter) {
-            displayCertChooserDialog(adapter);
-        }
     }
 
     private void displayCertChooserDialog(final CertificateAdapter adapter) {
@@ -363,7 +417,7 @@
             case REQUEST_UNLOCK:
                 if (mKeyStore.isUnlocked()) {
                     mState = State.INITIAL;
-                    showCertChooserDialog();
+                    chooseCertificate();
                 } else {
                     // user must have canceled unlock, give up
                     mState = State.UNLOCK_CANCELED;