Add new DevicePolicyManager API to allow fine-grained TrustAgent management

This adds a new feature that allows a device admin to specify a
whitelist of features that are allowed for the given admin.

Change-Id: I83f853318efbcf72308532d0a997374f73fa9c10
diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
index 51009af..0acb82f 100644
--- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java
+++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
@@ -16,10 +16,14 @@
 
 package com.android.server.trust;
 
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.ServiceConnection;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
@@ -30,6 +34,10 @@
 import android.util.Slog;
 import android.service.trust.ITrustAgentService;
 import android.service.trust.ITrustAgentServiceCallback;
+import android.service.trust.TrustAgentService;
+
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * A wrapper around a TrustAgentService interface. Coordinates communication between
@@ -43,6 +51,7 @@
     private static final int MSG_REVOKE_TRUST = 2;
     private static final int MSG_TRUST_TIMEOUT = 3;
     private static final int MSG_RESTART_TIMEOUT = 4;
+    private static final int MSG_DPM_CHANGED = 5;
 
     /**
      * Time in uptime millis that we wait for the service connection, both when starting
@@ -67,6 +76,7 @@
     // Trust state
     private boolean mTrusted;
     private CharSequence mMessage;
+    private boolean mTrustDisabledByDpm;
 
     private final Handler mHandler = new Handler() {
         @Override
@@ -109,6 +119,9 @@
                     unbind();
                     mTrustManagerService.resetAgent(mName, mUserId);
                     break;
+                case MSG_DPM_CHANGED:
+                    updateDevicePolicyFeatures(mName);
+                    break;
             }
         }
     };
@@ -141,6 +154,8 @@
             mTrustAgentService = ITrustAgentService.Stub.asInterface(service);
             mTrustManagerService.mArchive.logAgentConnected(mUserId, name);
             setCallback(mCallback);
+            updateDevicePolicyFeatures(name);
+            watchForDpmChanges(true);
         }
 
         @Override
@@ -152,9 +167,21 @@
             if (mBound) {
                 scheduleRestart();
             }
+            // mTrustDisabledByDpm maintains state
+            watchForDpmChanges(false);
         }
     };
 
+    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED
+                    .equals(intent.getAction())) {
+                mHandler.sendEmptyMessage(MSG_DPM_CHANGED);
+            }
+        }
+    };
 
     public TrustAgentWrapper(Context context, TrustManagerService trustManagerService,
             Intent intent, UserHandle user) {
@@ -196,8 +223,62 @@
         }
     }
 
+    private void watchForDpmChanges(boolean start) {
+        if (start) {
+            final IntentFilter filter = new IntentFilter();
+            filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
+            filter.addAction(Intent.ACTION_USER_REMOVED);
+            mContext.registerReceiver(mBroadcastReceiver, filter);
+        } else {
+            mContext.unregisterReceiver(mBroadcastReceiver);
+        }
+    }
+
+    private boolean updateDevicePolicyFeatures(ComponentName name) {
+        boolean trustDisabled = false;
+        if (DEBUG) Slog.v(TAG, "updateDevicePolicyFeatures(" + name + ")");
+        try {
+            if (mTrustAgentService != null) {
+                DevicePolicyManager dpm =
+                    (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+                if (dpm != null) {
+                    // If trust disabled, only enable it if the options bundle is set and
+                    // accepted by the TrustAgent.
+                    if ((dpm.getKeyguardDisabledFeatures(null)
+                            & DevicePolicyManager.KEYGUARD_DISABLE_TRUST_AGENTS) != 0) {
+                        List<String> features = dpm.getTrustAgentFeaturesEnabled(null, name);
+                        if (DEBUG) Slog.v(TAG, "Detected trust agents disabled. Features = " + features);
+                        if (features != null && features.size() > 0) {
+                            Bundle bundle = new Bundle();
+                            bundle.putStringArrayList(TrustAgentService.KEY_FEATURES,
+                                    (ArrayList<String>)features);
+                            if (DEBUG) {
+                                Slog.v(TAG, "TrustAgent " + name.flattenToShortString()
+                                        + " disabled except "+ features);
+                            }
+                            trustDisabled = mTrustAgentService.setTrustAgentFeaturesEnabled(bundle);
+                        } else {
+                            if (DEBUG) Slog.v(TAG, "TrustAgent " + name + " disabled by flag");
+                            trustDisabled = true; // trust agent should be disabled
+                        }
+                    }
+                } else {
+                    Log.e(TAG, "Can't get DevicePolicyManagerService: is it running?",
+                            new IllegalStateException("Stack trace:"));
+                }
+            }
+        } catch (RemoteException e) {
+            onError(e);
+        }
+        if (mTrustDisabledByDpm != trustDisabled) {
+            mTrustDisabledByDpm = trustDisabled;
+            mTrustManagerService.updateTrust(mUserId);
+        }
+        return trustDisabled;
+    }
+
     public boolean isTrusted() {
-        return mTrusted;
+        return mTrusted && !mTrustDisabledByDpm;
     }
 
     public CharSequence getMessage() {
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 14436aa..9c2df55 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -169,7 +169,7 @@
         for (UserInfo userInfo : userInfos) {
             int disabledFeatures = lockPatternUtils.getDevicePolicyManager()
                     .getKeyguardDisabledFeatures(null, userInfo.id);
-            boolean disableTrustAgents =
+            final boolean disableTrustAgents =
                     (disabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_TRUST_AGENTS) != 0;
 
             List<ComponentName> enabledAgents = lockPatternUtils.getEnabledTrustAgents(userInfo.id);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 25f9e9b..f49451e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -80,6 +80,7 @@
 import android.security.IKeyChainService;
 import android.security.KeyChain;
 import android.security.KeyChain.KeyChainConnection;
+import android.service.trust.TrustAgentService;
 import android.util.Log;
 import android.util.PrintWriterPrinter;
 import android.util.Printer;
@@ -89,6 +90,11 @@
 import android.view.IWindowManager;
 
 import org.xmlpull.v1.XmlPullParser;
+
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.END_TAG;
+import static org.xmlpull.v1.XmlPullParser.TEXT;
+
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
 
@@ -111,6 +117,8 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 
 /**
@@ -260,6 +268,9 @@
         private static final String TAG_DISABLE_ACCOUNT_MANAGEMENT = "disable-account-management";
         private static final String TAG_ACCOUNT_TYPE = "account-type";
         private static final String TAG_ENCRYPTION_REQUESTED = "encryption-requested";
+        private static final String TAG_MANAGE_TRUST_AGENT_FEATURES = "manage-trust-agent-features";
+        private static final String TAG_TRUST_AGENT_FEATURE = "feature";
+        private static final String TAG_TRUST_AGENT_COMPONENT = "component";
         private static final String TAG_PASSWORD_EXPIRATION_DATE = "password-expiration-date";
         private static final String TAG_PASSWORD_EXPIRATION_TIMEOUT = "password-expiration-timeout";
         private static final String TAG_GLOBAL_PROXY_EXCLUSION_LIST = "global-proxy-exclusion-list";
@@ -333,6 +344,7 @@
         boolean specifiesGlobalProxy = false;
         String globalProxySpec = null;
         String globalProxyExclusionList = null;
+        HashMap<String, List<String>> trustAgentFeatures = new HashMap<String, List<String>>();
 
         ActiveAdmin(DeviceAdminInfo _info) {
             info = _info;
@@ -463,15 +475,30 @@
                 }
                 out.endTag(null,  TAG_DISABLE_ACCOUNT_MANAGEMENT);
             }
+            if (!trustAgentFeatures.isEmpty()) {
+                Set<Entry<String, List<String>>> set = trustAgentFeatures.entrySet();
+                out.startTag(null, TAG_MANAGE_TRUST_AGENT_FEATURES);
+                for (Entry<String, List<String>> component : set) {
+                    out.startTag(null, TAG_TRUST_AGENT_COMPONENT);
+                    out.attribute(null, ATTR_VALUE, component.getKey());
+                    for (String feature : component.getValue()) {
+                        out.startTag(null, TAG_TRUST_AGENT_FEATURE);
+                        out.attribute(null, ATTR_VALUE, feature);
+                        out.endTag(null, TAG_TRUST_AGENT_FEATURE);
+                    }
+                    out.endTag(null, TAG_TRUST_AGENT_COMPONENT);
+                }
+                out.endTag(null, TAG_MANAGE_TRUST_AGENT_FEATURES);
+            }
         }
 
         void readFromXml(XmlPullParser parser)
                 throws XmlPullParserException, IOException {
             int outerDepth = parser.getDepth();
             int type;
-            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
-                   && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+            while ((type=parser.next()) != END_DOCUMENT
+                   && (type != END_TAG || parser.getDepth() > outerDepth)) {
+                if (type == END_TAG || type == TEXT) {
                     continue;
                 }
                 String tag = parser.getName();
@@ -541,22 +568,9 @@
                     disabledKeyguardFeatures = Integer.parseInt(
                             parser.getAttributeValue(null, ATTR_VALUE));
                 } else if (TAG_DISABLE_ACCOUNT_MANAGEMENT.equals(tag)) {
-                    int outerDepthDAM = parser.getDepth();
-                    int typeDAM;
-                    while ((typeDAM=parser.next()) != XmlPullParser.END_DOCUMENT
-                            && (typeDAM != XmlPullParser.END_TAG
-                                    || parser.getDepth() > outerDepthDAM)) {
-                        if (typeDAM == XmlPullParser.END_TAG || typeDAM == XmlPullParser.TEXT) {
-                            continue;
-                        }
-                        String tagDAM = parser.getName();
-                        if (TAG_ACCOUNT_TYPE.equals(tagDAM)) {
-                            accountTypesWithManagementDisabled.add(
-                                    parser.getAttributeValue(null, ATTR_VALUE));
-                        } else {
-                            Slog.w(LOG_TAG, "Unknown tag under " + tag +  ": " + tagDAM);
-                        }
-                    }
+                    accountTypesWithManagementDisabled = readDisableAccountInfo(parser, tag);
+                } else if (TAG_MANAGE_TRUST_AGENT_FEATURES.equals(tag)) {
+                    trustAgentFeatures = getAllTrustAgentFeatures(parser, tag);
                 } else {
                     Slog.w(LOG_TAG, "Unknown admin tag: " + tag);
                 }
@@ -564,6 +578,68 @@
             }
         }
 
+        private Set<String> readDisableAccountInfo(XmlPullParser parser, String tag)
+                throws XmlPullParserException, IOException {
+            int outerDepthDAM = parser.getDepth();
+            int typeDAM;
+            Set<String> result = new HashSet<String>();
+            while ((typeDAM=parser.next()) != END_DOCUMENT
+                    && (typeDAM != END_TAG || parser.getDepth() > outerDepthDAM)) {
+                if (typeDAM == END_TAG || typeDAM == TEXT) {
+                    continue;
+                }
+                String tagDAM = parser.getName();
+                if (TAG_ACCOUNT_TYPE.equals(tagDAM)) {
+                    result.add(parser.getAttributeValue(null, ATTR_VALUE));
+                } else {
+                    Slog.w(LOG_TAG, "Unknown tag under " + tag +  ": " + tagDAM);
+                }
+            }
+            return result;
+        }
+
+        private HashMap<String, List<String>> getAllTrustAgentFeatures(XmlPullParser parser,
+                String tag) throws XmlPullParserException, IOException {
+            int outerDepthDAM = parser.getDepth();
+            int typeDAM;
+            HashMap<String, List<String>> result = new HashMap<String, List<String>>();
+            while ((typeDAM=parser.next()) != END_DOCUMENT
+                    && (typeDAM != END_TAG || parser.getDepth() > outerDepthDAM)) {
+                if (typeDAM == END_TAG || typeDAM == TEXT) {
+                    continue;
+                }
+                String tagDAM = parser.getName();
+                if (TAG_TRUST_AGENT_COMPONENT.equals(tagDAM)) {
+                    final String component = parser.getAttributeValue(null, ATTR_VALUE);
+                    result.put(component, getTrustAgentFeatures(parser, tag));
+                } else {
+                    Slog.w(LOG_TAG, "Unknown tag under " + tag +  ": " + tagDAM);
+                }
+            }
+            return result;
+        }
+
+        private List<String> getTrustAgentFeatures(XmlPullParser parser, String tag)
+                throws XmlPullParserException, IOException  {
+            int outerDepthDAM = parser.getDepth();
+            int typeDAM;
+            ArrayList<String> result = new ArrayList<String>();
+            while ((typeDAM=parser.next()) != END_DOCUMENT
+                    && (typeDAM != END_TAG || parser.getDepth() > outerDepthDAM)) {
+                if (typeDAM == END_TAG || typeDAM == TEXT) {
+                    continue;
+                }
+                String tagDAM = parser.getName();
+                if (TAG_TRUST_AGENT_FEATURE.equals(tagDAM)) {
+                    final String feature = parser.getAttributeValue(null, ATTR_VALUE);
+                    result.add(feature);
+                } else {
+                    Slog.w(LOG_TAG, "Unknown tag under " + tag +  ": " + tagDAM);
+                }
+            }
+            return result;
+        }
+
         void dump(String prefix, PrintWriter pw) {
             pw.print(prefix); pw.print("uid="); pw.println(getUid());
             pw.print(prefix); pw.println("policies:");
@@ -3533,6 +3609,76 @@
         }
     }
 
+    public void setTrustAgentFeaturesEnabled(ComponentName admin, ComponentName agent,
+            List<String>features, int userHandle) {
+        if (!mHasFeature) {
+            return;
+        }
+        enforceCrossUserPermission(userHandle);
+        enforceNotManagedProfile(userHandle, "manage trust agent features");
+        synchronized (this) {
+            if (admin == null) {
+                throw new NullPointerException("admin is null");
+            }
+            if (agent == null) {
+                throw new NullPointerException("agent is null");
+            }
+            ActiveAdmin ap = getActiveAdminForCallerLocked(admin,
+                    DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES);
+            ap.trustAgentFeatures.put(agent.flattenToString(), features);
+            saveSettingsLocked(userHandle);
+            syncDeviceCapabilitiesLocked(getUserData(userHandle));
+        }
+    }
+
+    public List<String> getTrustAgentFeaturesEnabled(ComponentName admin, ComponentName agent,
+            int userHandle) {
+        if (!mHasFeature) {
+            return null;
+        }
+        enforceCrossUserPermission(userHandle);
+        synchronized (this) {
+            if (agent == null) {
+                throw new NullPointerException("agent is null");
+            }
+            final String componentName = agent.flattenToString();
+            if (admin != null) {
+                final ActiveAdmin ap = getActiveAdminUncheckedLocked(admin, userHandle);
+                return (ap != null) ? ap.trustAgentFeatures.get(componentName) : null;
+            }
+
+            // Return strictest policy for this user and profiles that are visible from this user.
+            List<UserInfo> profiles = mUserManager.getProfiles(userHandle);
+            List<String> result = null;
+            for (UserInfo userInfo : profiles) {
+                DevicePolicyData policy = getUserData(userInfo.getUserHandle().getIdentifier());
+                final int N = policy.mAdminList.size();
+                for (int i=0; i<N; i++) {
+                    ActiveAdmin ap = policy.mAdminList.get(i);
+                    // Compute the intersection of all features for active admins that disable
+                    // trust agents:
+                    if ((ap.disabledKeyguardFeatures
+                            & DevicePolicyManager.KEYGUARD_DISABLE_TRUST_AGENTS) != 0) {
+                        final List<String> features = ap.trustAgentFeatures.get(componentName);
+                        if (result == null) {
+                            if (features == null || features.size() == 0) {
+                                result = new ArrayList<String>();
+                                Slog.w(LOG_TAG, "admin " + ap.info.getPackageName()
+                                    + " has null trust agent feature set; all will be disabled");
+                            } else {
+                                result = new ArrayList<String>(features.size());
+                                result.addAll(features);
+                            }
+                        } else {
+                            result.retainAll(features);
+                        }
+                    }
+                }
+            }
+            return result;
+        }
+    }
+
     @Override
     public void setRestrictionsProvider(ComponentName who, ComponentName permissionProvider) {
         synchronized (this) {