More work on device admins:

- You can now show a dynamic message to the user when asking to
  have your DeviceAdmin added.
- A DeviceAdmin can now provide a warning message that is displayed
  before a user disables it.
- Better ordering (and text) of the policy warnings.
- New API to set the maximum failed password attempts before the device
  wipes itself.
- We now store the number of failed unlock attempts in persistent
  storage.
- New managed dialog APIs that will be used by the settings app.

Also a little bit of cleanup as I was working on this - removed the
long unused MailboxNotAvailableException, fixed a java doc in Messenger.
diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java
index fbd5317..ebd6f3d 100644
--- a/services/java/com/android/server/DevicePolicyManagerService.java
+++ b/services/java/com/android/server/DevicePolicyManagerService.java
@@ -23,19 +23,23 @@
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
 
+import android.app.Activity;
 import android.app.DeviceAdmin;
 import android.app.DeviceAdminInfo;
 import android.app.DevicePolicyManager;
 import android.app.IDevicePolicyManager;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.os.Binder;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.IPowerManager;
 import android.os.RecoverySystem;
+import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.util.Log;
@@ -64,16 +68,68 @@
     ActiveAdmin mActiveAdmin;
     
     static class ActiveAdmin {
-        ActiveAdmin(DeviceAdminInfo _info) {
-            info = _info;
-        }
-        
         final DeviceAdminInfo info;
-        int getUid() { return info.getActivityInfo().applicationInfo.uid; }
         
         int passwordMode = DevicePolicyManager.PASSWORD_MODE_UNSPECIFIED;
         int minimumPasswordLength = 0;
         long maximumTimeToUnlock = 0;
+        int maximumFailedPasswordsForWipe = 0;
+        
+        ActiveAdmin(DeviceAdminInfo _info) {
+            info = _info;
+        }
+        
+        int getUid() { return info.getActivityInfo().applicationInfo.uid; }
+        
+        void writeToXml(XmlSerializer out)
+                throws IllegalArgumentException, IllegalStateException, IOException {
+            if (passwordMode != DevicePolicyManager.PASSWORD_MODE_UNSPECIFIED) {
+                out.startTag(null, "password-mode");
+                out.attribute(null, "value", Integer.toString(passwordMode));
+                out.endTag(null, "password-mode");
+                if (minimumPasswordLength > 0) {
+                    out.startTag(null, "min-password-length");
+                    out.attribute(null, "value", Integer.toString(minimumPasswordLength));
+                    out.endTag(null, "mn-password-length");
+                }
+            }
+            if (maximumTimeToUnlock != DevicePolicyManager.PASSWORD_MODE_UNSPECIFIED) {
+                out.startTag(null, "max-time-to-unlock");
+                out.attribute(null, "value", Long.toString(maximumTimeToUnlock));
+                out.endTag(null, "max-time-to-unlock");
+            }
+            if (maximumFailedPasswordsForWipe != 0) {
+                out.startTag(null, "max-failed-password-wipe");
+                out.attribute(null, "value", Integer.toString(maximumFailedPasswordsForWipe));
+                out.endTag(null, "max-failed-password-wipe");
+            }
+        }
+        
+        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) {
+                    continue;
+                }
+                String tag = parser.getName();
+                if ("password-mode".equals(tag)) {
+                    passwordMode = Integer.parseInt(
+                            parser.getAttributeValue(null, "value"));
+                } else if ("min-password-length".equals(tag)) {
+                    minimumPasswordLength = Integer.parseInt(
+                            parser.getAttributeValue(null, "value"));
+                } else if ("max-time-to-unlock".equals(tag)) {
+                    maximumTimeToUnlock = Long.parseLong(
+                            parser.getAttributeValue(null, "value"));
+                } else if ("max-failed-password-wipe".equals(tag)) {
+                    maximumFailedPasswordsForWipe = Integer.parseInt(
+                            parser.getAttributeValue(null, "value"));
+                }
+            }
+        }
     }
     
     /**
@@ -91,25 +147,42 @@
         return mIPowerManager;
     }
     
-    ActiveAdmin getActiveAdminForCallerLocked(ComponentName who, int reqPolicy)
+    ActiveAdmin getActiveAdminUncheckedLocked(ComponentName who) {
+        ActiveAdmin admin = mActiveAdmin;
+        if (admin != null
+                && who.getPackageName().equals(admin.info.getActivityInfo().packageName)
+                && who.getClassName().equals(admin.info.getActivityInfo().name)) {
+            return admin;
+        }
+        return null;
+    }
+    
+    ActiveAdmin getActiveAdminForCallerLocked(ComponentName who)
             throws SecurityException {
-        if (mActiveAdmin != null && mActiveAdmin.getUid() == Binder.getCallingUid()) {
+        ActiveAdmin admin = mActiveAdmin;
+        if (admin != null && admin.getUid() == Binder.getCallingUid()) {
             if (who != null) {
-                if (!who.getPackageName().equals(mActiveAdmin.info.getActivityInfo().packageName)
-                        || !who.getClassName().equals(mActiveAdmin.info.getActivityInfo().name)) {
+                if (!who.getPackageName().equals(admin.info.getActivityInfo().packageName)
+                        || !who.getClassName().equals(admin.info.getActivityInfo().name)) {
                     throw new SecurityException("Current admin is not " + who);
                 }
             }
-            if (!mActiveAdmin.info.usesPolicy(reqPolicy)) {
-                throw new SecurityException("Admin " + mActiveAdmin.info.getComponent()
-                        + " did not specify uses-policy for: "
-                        + mActiveAdmin.info.getTagForPolicy(reqPolicy));
-            }
             return mActiveAdmin;
         }
         throw new SecurityException("Current admin is not owned by uid " + Binder.getCallingUid());
     }
     
+    ActiveAdmin getActiveAdminForCallerLocked(ComponentName who, int reqPolicy)
+            throws SecurityException {
+        ActiveAdmin admin = getActiveAdminForCallerLocked(who);
+        if (!admin.info.usesPolicy(reqPolicy)) {
+            throw new SecurityException("Admin " + admin.info.getComponent()
+                    + " did not specify uses-policy for: "
+                    + admin.info.getTagForPolicy(reqPolicy));
+        }
+        return admin;
+    }
+    
     void sendAdminCommandLocked(ActiveAdmin admin, String action) {
         Intent intent = new Intent(action);
         intent.setComponent(admin.info.getComponent());
@@ -182,25 +255,17 @@
             if (ap != null) {
                 out.startTag(null, "admin");
                 out.attribute(null, "name", ap.info.getComponent().flattenToString());
-                if (ap.passwordMode != DevicePolicyManager.PASSWORD_MODE_UNSPECIFIED) {
-                    out.startTag(null, "password-mode");
-                    out.attribute(null, "value", Integer.toString(ap.passwordMode));
-                    out.endTag(null, "password-mode");
-                    if (ap.minimumPasswordLength > 0) {
-                        out.startTag(null, "min-password-length");
-                        out.attribute(null, "value", Integer.toString(ap.minimumPasswordLength));
-                        out.endTag(null, "mn-password-length");
-                    }
-                }
-                if (ap.maximumTimeToUnlock != DevicePolicyManager.PASSWORD_MODE_UNSPECIFIED) {
-                    out.startTag(null, "max-time-to-unlock");
-                    out.attribute(null, "value", Long.toString(ap.maximumTimeToUnlock));
-                    out.endTag(null, "max-time-to-unlock");
-                }
+                ap.writeToXml(out);
                 out.endTag(null, "admin");
             }
             out.endTag(null, "policies");
 
+            if (mFailedPasswordAttempts != 0) {
+                out.startTag(null, "failed-password-attempts");
+                out.attribute(null, "value", Integer.toString(mFailedPasswordAttempts));
+                out.endTag(null, "failed-password-attempts");
+            }
+            
             out.endDocument();
             stream.close();
             journal.commit();
@@ -220,51 +285,41 @@
         JournaledFile journal = makeJournaledFile();
         FileInputStream stream = null;
         File file = journal.chooseForRead();
-        boolean success = false;
         try {
             stream = new FileInputStream(file);
             XmlPullParser parser = Xml.newPullParser();
             parser.setInput(stream, null);
 
-            int type = parser.next();
-            while (type != XmlPullParser.START_TAG) {
-                type = parser.next();
+            int type;
+            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+                    && type != XmlPullParser.START_TAG) {
             }
             String tag = parser.getName();
-            if ("policies".equals(tag)) {
-                ActiveAdmin ap = null;
-                do {
-                    type = parser.next();
-                    if (type == XmlPullParser.START_TAG) {
-                        tag = parser.getName();
-                        if (ap == null) {
-                            if ("admin".equals(tag)) {
-                                DeviceAdminInfo dai = findAdmin(
-                                        ComponentName.unflattenFromString(
-                                                parser.getAttributeValue(null, "name")));
-                                if (dai != null) {
-                                    ap = new ActiveAdmin(dai);
-                                }
-                            }
-                        } else if ("password-mode".equals(tag)) {
-                            ap.passwordMode = Integer.parseInt(
-                                    parser.getAttributeValue(null, "value"));
-                        } else if ("min-password-length".equals(tag)) {
-                            ap.minimumPasswordLength = Integer.parseInt(
-                                    parser.getAttributeValue(null, "value"));
-                        } else if ("max-time-to-unlock".equals(tag)) {
-                            ap.maximumTimeToUnlock = Long.parseLong(
-                                    parser.getAttributeValue(null, "value"));
-                        }
-                    } else if (type == XmlPullParser.END_TAG) {
-                        tag = parser.getName();
-                        if (ap != null && "admin".equals(tag)) {
-                            mActiveAdmin = ap;
-                            ap = null;
-                        }
+            if (!"policies".equals(tag)) {
+                throw new XmlPullParserException(
+                        "Settings do not start with policies tag: found " + tag);
+            }
+            type = parser.next();
+            int outerDepth = parser.getDepth();
+            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+                   && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                    continue;
+                }
+                tag = parser.getName();
+                if ("admin".equals(tag)) {
+                    DeviceAdminInfo dai = findAdmin(
+                            ComponentName.unflattenFromString(
+                                    parser.getAttributeValue(null, "name")));
+                    if (dai != null) {
+                        ActiveAdmin ap = new ActiveAdmin(dai);
+                        ap.readFromXml(parser);
+                        mActiveAdmin = ap;
                     }
-                } while (type != XmlPullParser.END_DOCUMENT);
-                success = true;
+                } else if ("failed-password-attempts".equals(tag)) {
+                    mFailedPasswordAttempts = Integer.parseInt(
+                            parser.getAttributeValue(null, "value"));
+                }
             }
         } catch (NullPointerException e) {
             Log.w(TAG, "failed parsing " + file + " " + e);
@@ -285,10 +340,6 @@
             // Ignore
         }
 
-        if (!success) {
-            Log.w(TAG, "No valid start tag found in policies file");
-        }
-        
         long timeMs = getMaximumTimeToLock();
         if (timeMs <= 0) {
             timeMs = Integer.MAX_VALUE;
@@ -418,6 +469,27 @@
         }
     }
     
+    public void setMaximumFailedPasswordsForWipe(ComponentName who, int num) {
+        synchronized (this) {
+            // This API can only be called by an active device admin,
+            // so try to retrieve it to check that the caller is one.
+            getActiveAdminForCallerLocked(who,
+                    DeviceAdminInfo.USES_POLICY_WIPE_DATA);
+            ActiveAdmin ap = getActiveAdminForCallerLocked(who,
+                    DeviceAdminInfo.USES_POLICY_WATCH_LOGIN);
+            if (ap.maximumFailedPasswordsForWipe != num) {
+                ap.maximumFailedPasswordsForWipe = num;
+                saveSettingsLocked();
+            }
+        }
+    }
+    
+    public int getMaximumFailedPasswordsForWipe() {
+        synchronized (this) {
+            return mActiveAdmin != null ? mActiveAdmin.maximumFailedPasswordsForWipe : 0;
+        }
+    }
+    
     public boolean resetPassword(String password) {
         int mode;
         synchronized (this) {
@@ -488,20 +560,53 @@
         }
     }
     
+    void wipeDataLocked(int flags) {
+        try {
+            RecoverySystem.rebootWipeUserData(mContext);
+        } catch (IOException e) {
+            Log.w(TAG, "Failed requesting data wipe", e);
+        }
+    }
+    
     public void wipeData(int flags) {
         synchronized (this) {
             // This API can only be called by an active device admin,
             // so try to retrieve it to check that the caller is one.
             getActiveAdminForCallerLocked(null,
                     DeviceAdminInfo.USES_POLICY_WIPE_DATA);
+            long ident = Binder.clearCallingIdentity();
+            try {
+                wipeDataLocked(flags);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
         }
-        long ident = Binder.clearCallingIdentity();
-        try {
-            RecoverySystem.rebootWipeUserData(mContext);
-        } catch (IOException e) {
-            Log.w(TAG, "Failed requesting data wipe", e);
-        } finally {
-            Binder.restoreCallingIdentity(ident);
+    }
+    
+    public void getRemoveWarning(ComponentName comp, final RemoteCallback result) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.BIND_DEVICE_ADMIN, null);
+        
+        synchronized (this) {
+            ActiveAdmin admin = getActiveAdminUncheckedLocked(comp);
+            if (admin == null) {
+                try {
+                    result.sendResult(null);
+                } catch (RemoteException e) {
+                }
+                return;
+            }
+            Intent intent = new Intent(DeviceAdmin.ACTION_DEVICE_ADMIN_DISABLE_REQUESTED);
+            intent.setComponent(admin.info.getComponent());
+            mContext.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    try {
+                        result.sendResult(getResultExtras(false));
+                    } catch (RemoteException e) {
+                    }
+                }
+            }, null, Activity.RESULT_OK, null, null);
         }
     }
     
@@ -516,7 +621,10 @@
                 try {
                     mActivePasswordMode = mode;
                     mActivePasswordLength = length;
-                    mFailedPasswordAttempts = 0;
+                    if (mFailedPasswordAttempts != 0) {
+                        mFailedPasswordAttempts = 0;
+                        saveSettingsLocked();
+                    }
                     sendAdminCommandLocked(DeviceAdmin.ACTION_PASSWORD_CHANGED,
                             DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
                 } finally {
@@ -534,6 +642,11 @@
             long ident = Binder.clearCallingIdentity();
             try {
                 mFailedPasswordAttempts++;
+                saveSettingsLocked();
+                int max = getMaximumFailedPasswordsForWipe();
+                if (max > 0 && mFailedPasswordAttempts >= max) {
+                    wipeDataLocked(0);
+                }
                 sendAdminCommandLocked(DeviceAdmin.ACTION_PASSWORD_FAILED,
                         DeviceAdminInfo.USES_POLICY_WATCH_LOGIN);
             } finally {
@@ -551,6 +664,7 @@
                 long ident = Binder.clearCallingIdentity();
                 try {
                     mFailedPasswordAttempts = 0;
+                    saveSettingsLocked();
                     sendAdminCommandLocked(DeviceAdmin.ACTION_PASSWORD_SUCCEEDED,
                             DeviceAdminInfo.USES_POLICY_WATCH_LOGIN);
                 } finally {