OSDN Git Service

More work on device admins:
authorDianne Hackborn <hackbod@google.com>
Wed, 27 Jan 2010 02:01:04 +0000 (18:01 -0800)
committerDianne Hackborn <hackbod@google.com>
Wed, 27 Jan 2010 21:51:53 +0000 (13:51 -0800)
- 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.

14 files changed:
Android.mk
api/current.xml
core/java/android/app/Activity.java
core/java/android/app/DeviceAdmin.java
core/java/android/app/DeviceAdminInfo.java
core/java/android/app/DevicePolicyManager.java
core/java/android/app/IDevicePolicyManager.aidl
core/java/android/os/IRemoteCallback.aidl [new file with mode: 0644]
core/java/android/os/MailboxNotAvailableException.java [deleted file]
core/java/android/os/Messenger.java
core/java/android/os/RemoteCallback.aidl [new file with mode: 0644]
core/java/android/os/RemoteCallback.java [new file with mode: 0644]
core/res/res/values/strings.xml
services/java/com/android/server/DevicePolicyManagerService.java

index 5f9bea4..7520afe 100644 (file)
@@ -117,6 +117,7 @@ LOCAL_SRC_FILES += \
        core/java/android/os/IParentalControlCallback.aidl \
        core/java/android/os/IPermissionController.aidl \
        core/java/android/os/IPowerManager.aidl \
+    core/java/android/os/IRemoteCallback.aidl \
        core/java/android/os/IVibratorService.aidl \
     core/java/android/service/wallpaper/IWallpaperConnection.aidl \
     core/java/android/service/wallpaper/IWallpaperEngine.aidl \
index 0d16f67..1250461 100644 (file)
  synchronized="false"
  static="false"
  final="false"
+ deprecated="deprecated"
+ visibility="protected"
+>
+<parameter name="id" type="int">
+</parameter>
+</method>
+<method name="onCreateDialog"
+ return="android.app.Dialog"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
  deprecated="not deprecated"
  visibility="protected"
 >
 <parameter name="id" type="int">
 </parameter>
+<parameter name="args" type="android.os.Bundle">
+</parameter>
 </method>
 <method name="onCreateOptionsMenu"
  return="boolean"
  synchronized="false"
  static="false"
  final="false"
+ deprecated="deprecated"
+ visibility="protected"
+>
+<parameter name="id" type="int">
+</parameter>
+<parameter name="dialog" type="android.app.Dialog">
+</parameter>
+</method>
+<method name="onPrepareDialog"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
  deprecated="not deprecated"
  visibility="protected"
 >
 </parameter>
 <parameter name="dialog" type="android.app.Dialog">
 </parameter>
+<parameter name="args" type="android.os.Bundle">
+</parameter>
 </method>
 <method name="onPrepareOptionsMenu"
  return="boolean"
 <parameter name="id" type="int">
 </parameter>
 </method>
+<method name="showDialog"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="id" type="int">
+</parameter>
+<parameter name="args" type="android.os.Bundle">
+</parameter>
+</method>
 <method name="startActivityForResult"
  return="void"
  abstract="false"
 <parameter name="context" type="android.content.Context">
 </parameter>
 </method>
+<method name="onDisableRequested"
+ return="java.lang.CharSequence"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="intent" type="android.content.Intent">
+</parameter>
+</method>
 <method name="onDisabled"
  return="void"
  abstract="false"
  visibility="public"
 >
 </field>
+<field name="ACTION_DEVICE_ADMIN_DISABLE_REQUESTED"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="ACTION_DEVICE_ADMIN_ENABLED"
  type="java.lang.String"
  transient="false"
  visibility="public"
 >
 </field>
+<field name="EXTRA_DISABLE_WARNING"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.app.extra.DISABLE_WARNING&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 </class>
 <class name="DeviceAdminInfo"
  extends="java.lang.Object"
 <parameter name="password" type="java.lang.String">
 </parameter>
 </method>
+<method name="setMaximumFailedPasswordsForWipe"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="admin" type="android.content.ComponentName">
+</parameter>
+<parameter name="num" type="int">
+</parameter>
+</method>
 <method name="setMaximumTimeToLock"
  return="void"
  abstract="false"
  visibility="public"
 >
 </field>
+<field name="EXTRA_ADD_EXPLANATION"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.app.extra.ADD_EXPLANATION&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="EXTRA_DEVICE_ADMIN"
  type="java.lang.String"
  transient="false"
index ca15a99..95142e3 100644 (file)
@@ -609,8 +609,13 @@ public class Activity extends ContextThemeWrapper
     private static final String SAVED_DIALOG_IDS_KEY = "android:savedDialogIds";
     private static final String SAVED_DIALOGS_TAG = "android:savedDialogs";
     private static final String SAVED_DIALOG_KEY_PREFIX = "android:dialog_";
+    private static final String SAVED_DIALOG_ARGS_KEY_PREFIX = "android:dialog_args_";
 
-    private SparseArray<Dialog> mManagedDialogs;
+    private static class ManagedDialog {
+        Dialog mDialog;
+        Bundle mArgs;
+    }
+    private SparseArray<ManagedDialog> mManagedDialogs;
 
     // set by the thread after the constructor and before onCreate(Bundle savedInstanceState) is called.
     private Instrumentation mInstrumentation;
@@ -851,35 +856,41 @@ public class Activity extends ContextThemeWrapper
 
         final int[] ids = b.getIntArray(SAVED_DIALOG_IDS_KEY);
         final int numDialogs = ids.length;
-        mManagedDialogs = new SparseArray<Dialog>(numDialogs);
+        mManagedDialogs = new SparseArray<ManagedDialog>(numDialogs);
         for (int i = 0; i < numDialogs; i++) {
             final Integer dialogId = ids[i];
             Bundle dialogState = b.getBundle(savedDialogKeyFor(dialogId));
             if (dialogState != null) {
                 // Calling onRestoreInstanceState() below will invoke dispatchOnCreate
                 // so tell createDialog() not to do it, otherwise we get an exception
-                final Dialog dialog = createDialog(dialogId, dialogState);
-                mManagedDialogs.put(dialogId, dialog);
-                onPrepareDialog(dialogId, dialog);
-                dialog.onRestoreInstanceState(dialogState);
+                final ManagedDialog md = new ManagedDialog();
+                md.mArgs = b.getBundle(savedDialogArgsKeyFor(dialogId));
+                md.mDialog = createDialog(dialogId, dialogState, md.mArgs);
+                if (md.mDialog != null) {
+                    mManagedDialogs.put(dialogId, md);
+                    onPrepareDialog(dialogId, md.mDialog, md.mArgs);
+                    md.mDialog.onRestoreInstanceState(dialogState);
+                }
             }
         }
     }
 
-    private Dialog createDialog(Integer dialogId, Bundle state) {
-        final Dialog dialog = onCreateDialog(dialogId);
+    private Dialog createDialog(Integer dialogId, Bundle state, Bundle args) {
+        final Dialog dialog = onCreateDialog(dialogId, args);
         if (dialog == null) {
-            throw new IllegalArgumentException("Activity#onCreateDialog did "
-                    + "not create a dialog for id " + dialogId);
+            return null;
         }
         dialog.dispatchOnCreate(state);
         return dialog;
     }
 
-    private String savedDialogKeyFor(int key) {
+    private static String savedDialogKeyFor(int key) {
         return SAVED_DIALOG_KEY_PREFIX + key;
     }
 
+    private static String savedDialogArgsKeyFor(int key) {
+        return SAVED_DIALOG_ARGS_KEY_PREFIX + key;
+    }
 
     /**
      * Called when activity start-up is complete (after {@link #onStart}
@@ -1096,8 +1107,11 @@ public class Activity extends ContextThemeWrapper
         for (int i = 0; i < numDialogs; i++) {
             final int key = mManagedDialogs.keyAt(i);
             ids[i] = key;
-            final Dialog dialog = mManagedDialogs.valueAt(i);
-            dialogState.putBundle(savedDialogKeyFor(key), dialog.onSaveInstanceState());
+            final ManagedDialog md = mManagedDialogs.valueAt(i);
+            dialogState.putBundle(savedDialogKeyFor(key), md.mDialog.onSaveInstanceState());
+            if (md.mArgs != null) {
+                dialogState.putBundle(savedDialogArgsKeyFor(key), md.mArgs);
+            }
         }
 
         dialogState.putIntArray(SAVED_DIALOG_IDS_KEY, ids);
@@ -1283,14 +1297,14 @@ public class Activity extends ContextThemeWrapper
 
         // dismiss any dialogs we are managing.
         if (mManagedDialogs != null) {
-
             final int numDialogs = mManagedDialogs.size();
             for (int i = 0; i < numDialogs; i++) {
-                final Dialog dialog = mManagedDialogs.valueAt(i);
-                if (dialog.isShowing()) {
-                    dialog.dismiss();
+                final ManagedDialog md = mManagedDialogs.valueAt(i);
+                if (md.mDialog.isShowing()) {
+                    md.mDialog.dismiss();
                 }
             }
+            mManagedDialogs = null;
         }
 
         // close any cursors we are managing.
@@ -1301,6 +1315,7 @@ public class Activity extends ContextThemeWrapper
                 c.mCursor.close();
             }
         }
+        mManagedCursors.clear();
     }
 
     /**
@@ -2411,36 +2426,57 @@ public class Activity extends ContextThemeWrapper
     }
 
     /**
+     * @deprecated Old no-arguments version of {@link #onCreateDialog(int, Bundle)}.
+     */
+    @Deprecated
+    protected Dialog onCreateDialog(int id) {
+        return null;
+    }
+
+    /**
      * Callback for creating dialogs that are managed (saved and restored) for you
-     * by the activity.
+     * by the activity.  The default implementation calls through to
+     * {@link #onCreateDialog(int)} for compatibility.
      *
-     * If you use {@link #showDialog(int)}, the activity will call through to
+     * <p>If you use {@link #showDialog(int)}, the activity will call through to
      * this method the first time, and hang onto it thereafter.  Any dialog
      * that is created by this method will automatically be saved and restored
      * for you, including whether it is showing.
      *
-     * If you would like the activity to manage the saving and restoring dialogs
+     * <p>If you would like the activity to manage saving and restoring dialogs
      * for you, you should override this method and handle any ids that are
      * passed to {@link #showDialog}.
      *
-     * If you would like an opportunity to prepare your dialog before it is shown,
-     * override {@link #onPrepareDialog(int, Dialog)}.
+     * <p>If you would like an opportunity to prepare your dialog before it is shown,
+     * override {@link #onPrepareDialog(int, Dialog, Bundle)}.
      *
      * @param id The id of the dialog.
-     * @return The dialog
+     * @param args The dialog arguments provided to {@link #showDialog(int, Bundle)}.
+     * @return The dialog.  If you return null, the dialog will not be created.
      *
-     * @see #onPrepareDialog(int, Dialog)
-     * @see #showDialog(int)
+     * @see #onPrepareDialog(int, Dialog, Bundle)
+     * @see #showDialog(int, Bundle)
      * @see #dismissDialog(int)
      * @see #removeDialog(int)
      */
-    protected Dialog onCreateDialog(int id) {
-        return null;
+    protected Dialog onCreateDialog(int id, Bundle args) {
+        return onCreateDialog(id);
+    }
+
+    /**
+     * @deprecated Old no-arguments version of
+     * {@link #onPrepareDialog(int, Dialog, Bundle)}.
+     */
+    @Deprecated
+    protected void onPrepareDialog(int id, Dialog dialog) {
+        dialog.setOwnerActivity(this);
     }
 
     /**
      * Provides an opportunity to prepare a managed dialog before it is being
-     * shown.
+     * shown.  The default implementation calls through to
+     * {@link #onPrepareDialog(int, Dialog)} for compatibility.
+     * 
      * <p>
      * Override this if you need to update a managed dialog based on the state
      * of the application each time it is shown. For example, a time picker
@@ -2450,43 +2486,66 @@ public class Activity extends ContextThemeWrapper
      * 
      * @param id The id of the managed dialog.
      * @param dialog The dialog.
-     * @see #onCreateDialog(int)
+     * @param args The dialog arguments provided to {@link #showDialog(int, Bundle)}.
+     * @see #onCreateDialog(int, Bundle)
      * @see #showDialog(int)
      * @see #dismissDialog(int)
      * @see #removeDialog(int)
      */
-    protected void onPrepareDialog(int id, Dialog dialog) {
-        dialog.setOwnerActivity(this);
+    protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {
+        onPrepareDialog(id, dialog);
     }
 
     /**
-     * Show a dialog managed by this activity.  A call to {@link #onCreateDialog(int)}
+     * Simple version of {@link #showDialog(int, Bundle)} that does not
+     * take any arguments.  Simply calls {@link #showDialog(int, Bundle)}
+     * with null arguments.
+     */
+    public final void showDialog(int id) {
+        showDialog(id, null);
+    }
+
+    /**
+     * Show a dialog managed by this activity.  A call to {@link #onCreateDialog(int, Bundle)}
      * will be made with the same id the first time this is called for a given
      * id.  From thereafter, the dialog will be automatically saved and restored.
      *
-     * Each time a dialog is shown, {@link #onPrepareDialog(int, Dialog)} will
+     * <p>Each time a dialog is shown, {@link #onPrepareDialog(int, Dialog, Bundle)} will
      * be made to provide an opportunity to do any timely preparation.
      *
      * @param id The id of the managed dialog.
-     *
+     * @param args Arguments to pass through to the dialog.  These will be saved
+     * and restored for you.  Note that if the dialog is already created,
+     * {@link #onCreateDialog(int, Bundle)} will not be called with the new
+     * arguments but {@link #onPrepareDialog(int, Dialog, Bundle)} will be.
+     * If you need to rebuild the dialog, call {@link #removeDialog(int)}Êfirst.
+     * @return Returns true if the Dialog was created; false is returned if
+     * it is not created because {@link #onCreateDialog(int, Bundle)} returns false.
+     * 
      * @see Dialog
-     * @see #onCreateDialog(int)
-     * @see #onPrepareDialog(int, Dialog)
+     * @see #onCreateDialog(int, Bundle)
+     * @see #onPrepareDialog(int, Dialog, Bundle)
      * @see #dismissDialog(int)
      * @see #removeDialog(int)
      */
-    public final void showDialog(int id) {
+    public final boolean showDialog(int id, Bundle args) {
         if (mManagedDialogs == null) {
-            mManagedDialogs = new SparseArray<Dialog>();
+            mManagedDialogs = new SparseArray<ManagedDialog>();
         }
-        Dialog dialog = mManagedDialogs.get(id);
-        if (dialog == null) {
-            dialog = createDialog(id, null);
-            mManagedDialogs.put(id, dialog);
+        ManagedDialog md = mManagedDialogs.get(id);
+        if (md == null) {
+            md = new ManagedDialog();
+            md.mDialog = createDialog(id, null, args);
+            if (md.mDialog == null) {
+                return false;
+            }
+            mManagedDialogs.put(id, md);
         }
         
-        onPrepareDialog(id, dialog);
-        dialog.show();
+        md.mArgs = args;
+        onPrepareDialog(id, md.mDialog, args);
+        md.mDialog.show();
+        return true;
     }
 
     /**
@@ -2497,21 +2556,21 @@ public class Activity extends ContextThemeWrapper
      * @throws IllegalArgumentException if the id was not previously shown via
      *   {@link #showDialog(int)}.
      *
-     * @see #onCreateDialog(int)
-     * @see #onPrepareDialog(int, Dialog)
+     * @see #onCreateDialog(int, Bundle)
+     * @see #onPrepareDialog(int, Dialog, Bundle)
      * @see #showDialog(int)
      * @see #removeDialog(int)
      */
     public final void dismissDialog(int id) {
         if (mManagedDialogs == null) {
             throw missingDialog(id);
-
         }
-        final Dialog dialog = mManagedDialogs.get(id);
-        if (dialog == null) {
+        
+        final ManagedDialog md = mManagedDialogs.get(id);
+        if (md == null) {
             throw missingDialog(id);
         }
-        dialog.dismiss();
+        md.mDialog.dismiss();
     }
 
     /**
@@ -2527,28 +2586,27 @@ public class Activity extends ContextThemeWrapper
      * Removes any internal references to a dialog managed by this Activity.
      * If the dialog is showing, it will dismiss it as part of the clean up.
      *
-     * This can be useful if you know that you will never show a dialog again and
+     * <p>This can be useful if you know that you will never show a dialog again and
      * want to avoid the overhead of saving and restoring it in the future.
      *
      * @param id The id of the managed dialog.
      *
-     * @see #onCreateDialog(int)
-     * @see #onPrepareDialog(int, Dialog)
+     * @see #onCreateDialog(int, Bundle)
+     * @see #onPrepareDialog(int, Dialog, Bundle)
      * @see #showDialog(int)
      * @see #dismissDialog(int)
      */
     public final void removeDialog(int id) {
-
         if (mManagedDialogs == null) {
             return;
         }
 
-        final Dialog dialog = mManagedDialogs.get(id);
-        if (dialog == null) {
+        final ManagedDialog md = mManagedDialogs.get(id);
+        if (md == null) {
             return;
         }
 
-        dialog.dismiss();
+        md.mDialog.dismiss();
         mManagedDialogs.remove(id);
     }
 
index 9750d6e..88fdab2 100644 (file)
@@ -22,6 +22,7 @@ import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.os.Bundle;
 
 /**
  * Base class for implementing a device administration component.  This
@@ -62,6 +63,27 @@ public class DeviceAdmin extends BroadcastReceiver {
             = "android.app.action.DEVICE_ADMIN_ENABLED";
 
     /**
+     * Action sent to a device administrator when the user has requested to
+     * disable it, but before this has actually been done.  This gives you
+     * a chance to supply a message to the user about the impact of
+     * disabling your admin, by setting the extra field
+     * {@link #EXTRA_DISABLE_WARNING} in the result Intent.  If not set,
+     * no warning will be displayed.  If set, the given text will be shown
+     * to the user before they disable your admin.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_DEVICE_ADMIN_DISABLE_REQUESTED
+            = "android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED";
+    
+    /**
+     * A CharSequence that can be shown to the user informing them of the
+     * impact of disabling your admin.
+     *
+     * @see #ACTION_DEVICE_ADMIN_DISABLE_REQUESTED
+     */
+    public static final String EXTRA_DISABLE_WARNING = "android.app.extra.DISABLE_WARNING";
+    
+    /**
      * Action sent to a device administrator when the user has disabled
      * it.  Upon return, the application no longer has access to the
      * protected device policy manager APIs.  You will generally
@@ -166,6 +188,21 @@ public class DeviceAdmin extends BroadcastReceiver {
     }
     
     /**
+     * Called when the user has asked to disable the administrator, as a result of
+     * receiving {@link #ACTION_DEVICE_ADMIN_DISABLE_REQUESTED}, giving you
+     * a chance to present a warning message to them.  The message is returned
+     * as the result; if null is returned (the default implementation), no
+     * message will be displayed.
+     * @param context The running context as per {@link #onReceive}.
+     * @param intent The received intent as per {@link #onReceive}.
+     * @return Return the warning message to display to the user before
+     * being disabled; if null is returned, no message is displayed.
+     */
+    public CharSequence onDisableRequested(Context context, Intent intent) {
+        return null;
+    }
+    
+    /**
      * Called prior to the administrator being disabled, as a result of
      * receiving {@link #ACTION_DEVICE_ADMIN_DISABLED}.  Upon return, you
      * can no longer use the protected parts of the {@link DevicePolicyManager}
@@ -226,6 +263,12 @@ public class DeviceAdmin extends BroadcastReceiver {
             onPasswordSucceeded(context, intent);
         } else if (ACTION_DEVICE_ADMIN_ENABLED.equals(action)) {
             onEnabled(context, intent);
+        } else if (ACTION_DEVICE_ADMIN_DISABLE_REQUESTED.equals(action)) {
+            CharSequence res = onDisableRequested(context, intent);
+            if (res != null) {
+                Bundle extras = getResultExtras(true);
+                extras.putCharSequence(EXTRA_DISABLE_WARNING, res);
+            }
         } else if (ACTION_DEVICE_ADMIN_DISABLED.equals(action)) {
             onDisabled(context, intent);
         }
index 92fdbc8..e50db89 100644 (file)
@@ -19,7 +19,6 @@ package android.app;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
-import android.R;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
@@ -111,48 +110,47 @@ public final class DeviceAdminInfo implements Parcelable {
 
     /** @hide */
     public static class PolicyInfo {
+        public final int ident;
         final public String tag;
         final public int label;
         final public int description;
         
-        public PolicyInfo(String tagIn, int labelIn, int descriptionIn) {
+        public PolicyInfo(int identIn, String tagIn, int labelIn, int descriptionIn) {
+            ident = identIn;
             tag = tagIn;
             label = labelIn;
             description = descriptionIn;
         }
     }
     
+    static ArrayList<PolicyInfo> sPoliciesDisplayOrder = new ArrayList<PolicyInfo>();
     static HashMap<String, Integer> sKnownPolicies = new HashMap<String, Integer>();
     static SparseArray<PolicyInfo> sRevKnownPolicies = new SparseArray<PolicyInfo>();
     
     static {
-        sRevKnownPolicies.put(USES_POLICY_LIMIT_PASSWORD,
-                new PolicyInfo("limit-password",
-                        com.android.internal.R.string.policylab_limitPassword,
-                        com.android.internal.R.string.policydesc_limitPassword));
-        sRevKnownPolicies.put(USES_POLICY_WATCH_LOGIN,
-                new PolicyInfo("watch-login",
-                        com.android.internal.R.string.policylab_watchLogin,
-                        com.android.internal.R.string.policydesc_watchLogin));
-        sRevKnownPolicies.put(USES_POLICY_RESET_PASSWORD,
-                new PolicyInfo("reset-password",
-                        com.android.internal.R.string.policylab_resetPassword,
-                        com.android.internal.R.string.policydesc_resetPassword));
-        sRevKnownPolicies.put(USES_POLICY_LIMIT_UNLOCK,
-                new PolicyInfo("limit-unlock",
-                        com.android.internal.R.string.policylab_limitUnlock,
-                        com.android.internal.R.string.policydesc_limitUnlock));
-        sRevKnownPolicies.put(USES_POLICY_FORCE_LOCK,
-                new PolicyInfo("force-lock",
-                        com.android.internal.R.string.policylab_forceLock,
-                        com.android.internal.R.string.policydesc_forceLock));
-        sRevKnownPolicies.put(USES_POLICY_WIPE_DATA,
-                new PolicyInfo("wipe-data",
-                        com.android.internal.R.string.policylab_wipeData,
-                        com.android.internal.R.string.policydesc_wipeData));
-        for (int i=0; i<sRevKnownPolicies.size(); i++) {
-            sKnownPolicies.put(sRevKnownPolicies.valueAt(i).tag,
-                    sRevKnownPolicies.keyAt(i));
+        sPoliciesDisplayOrder.add(new PolicyInfo(USES_POLICY_WIPE_DATA, "wipe-data",
+                com.android.internal.R.string.policylab_wipeData,
+                com.android.internal.R.string.policydesc_wipeData));
+        sPoliciesDisplayOrder.add(new PolicyInfo(USES_POLICY_RESET_PASSWORD, "reset-password",
+                com.android.internal.R.string.policylab_resetPassword,
+                com.android.internal.R.string.policydesc_resetPassword));
+        sPoliciesDisplayOrder.add(new PolicyInfo(USES_POLICY_LIMIT_PASSWORD, "limit-password",
+                com.android.internal.R.string.policylab_limitPassword,
+                com.android.internal.R.string.policydesc_limitPassword));
+        sPoliciesDisplayOrder.add(new PolicyInfo(USES_POLICY_WATCH_LOGIN, "watch-login",
+                com.android.internal.R.string.policylab_watchLogin,
+                com.android.internal.R.string.policydesc_watchLogin));
+        sPoliciesDisplayOrder.add(new PolicyInfo(USES_POLICY_LIMIT_UNLOCK, "limit-unlock",
+                com.android.internal.R.string.policylab_limitUnlock,
+                com.android.internal.R.string.policydesc_limitUnlock));
+        sPoliciesDisplayOrder.add(new PolicyInfo(USES_POLICY_FORCE_LOCK, "force-lock",
+                com.android.internal.R.string.policylab_forceLock,
+                com.android.internal.R.string.policydesc_forceLock));
+        
+        for (int i=0; i<sPoliciesDisplayOrder.size(); i++) {
+            PolicyInfo pi = sPoliciesDisplayOrder.get(i);
+            sRevKnownPolicies.put(pi.ident, pi);
+            sKnownPolicies.put(pi.tag, pi.ident);
         }
     }
     
@@ -335,10 +333,10 @@ public final class DeviceAdminInfo implements Parcelable {
     /** @hide */
     public ArrayList<PolicyInfo> getUsedPolicies() {
         ArrayList<PolicyInfo> res = new ArrayList<PolicyInfo>();
-        for (int i=0; i<sRevKnownPolicies.size(); i++) {
-            int ident = sRevKnownPolicies.keyAt(i);
-            if (usesPolicy(ident)) {
-                res.add(sRevKnownPolicies.valueAt(i));
+        for (int i=0; i<sPoliciesDisplayOrder.size(); i++) {
+            PolicyInfo pi = sPoliciesDisplayOrder.get(i);
+            if (usesPolicy(pi.ident)) {
+                res.add(pi);
             }
         }
         return res;
index 25e3230..9de7336 100644 (file)
@@ -26,6 +26,7 @@ import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.os.Handler;
+import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.util.Log;
@@ -43,8 +44,9 @@ public class DevicePolicyManager {
     private static boolean localLOGV = DEBUG || android.util.Config.LOGV;
 
     private final Context mContext;
-    private final Handler mHandler;
     private final IDevicePolicyManager mService;
+    
+    private final Handler mHandler;
 
     /*package*/ DevicePolicyManager(Context context, Handler handler) {
         mContext = context;
@@ -60,6 +62,10 @@ public class DevicePolicyManager {
      * bring the user through adding the device administrator to the system (or
      * allowing them to reject it).
      * 
+     * <p>You can optionally include the {@link #EXTRA_ADD_EXPLANATION}
+     * field to provide the user with additional explanation (in addition
+     * to your component's description) about what is being added.
+     * 
      * <p>Note: the current platform can only have one device administrator
      * active at a time.  If you make this request while there is already
      * an active administrator, this new request will be canceled automatically.
@@ -76,6 +82,14 @@ public class DevicePolicyManager {
     public static final String EXTRA_DEVICE_ADMIN = "android.app.extra.DEVICE_ADMIN";
     
     /**
+     * An optional CharSequence providing additional explanation for why the
+     * admin is being added.
+     *
+     * @see #ACTION_ADD_DEVICE_ADMIN
+     */
+    public static final String EXTRA_ADD_EXPLANATION = "android.app.extra.ADD_EXPLANATION";
+    
+    /**
      * Activity action: have the user enter a new password.  This activity
      * should be launched after using {@link #setPasswordMode(ComponentName, int)}
      * or {@link #setMinimumPasswordLength(ComponentName, int)} to have the
@@ -285,6 +299,29 @@ public class DevicePolicyManager {
     }
 
     /**
+     * Set the maximum number of failed password attempts that are allowed
+     * before the device wipes its data.  This is convenience for implementing
+     * the corresponding functionality with a combination of watching failed
+     * password attempts and calling {@link #wipeData} upon reaching a certain
+     * count, and as such requires that you request both
+     * {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} and
+     * {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}}.
+     * 
+     * @param admin Which {@link DeviceAdmin} this request is associated with.
+     * @param num The number of failed password attempts at which point the
+     * device will wipe its data.
+     */
+    public void setMaximumFailedPasswordsForWipe(ComponentName admin, int num) {
+        if (mService != null) {
+            try {
+                mService.setMaximumFailedPasswordsForWipe(admin, num);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed talking with device policy service", e);
+            }
+        }
+    }
+    
+    /**
      * Force a new password on the user.  This takes effect immediately.  The
      * given password must meet the current password minimum length constraint
      * or it will be rejected.  The given password will be accepted regardless
@@ -451,6 +488,19 @@ public class DevicePolicyManager {
     /**
      * @hide
      */
+    public void getRemoveWarning(ComponentName admin, RemoteCallback result) {
+        if (mService != null) {
+            try {
+                mService.getRemoveWarning(admin, result);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed talking with device policy service", e);
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
     public void setActivePasswordState(int mode, int length) {
         if (mService != null) {
             try {
index 7e38194..edb8603 100644 (file)
@@ -18,6 +18,7 @@
 package android.app;
 
 import android.content.ComponentName;
+import android.os.RemoteCallback;
 
 /**
  * Internal IPC interface to the device policy service.
@@ -32,6 +33,7 @@ interface IDevicePolicyManager {
     
     boolean isActivePasswordSufficient();
     int getCurrentFailedPasswordAttempts();
+    void setMaximumFailedPasswordsForWipe(in ComponentName admin, int num);
     
     boolean resetPassword(String password);
     
@@ -44,6 +46,7 @@ interface IDevicePolicyManager {
     
     void setActiveAdmin(in ComponentName policyReceiver);
     ComponentName getActiveAdmin();
+    void getRemoveWarning(in ComponentName policyReceiver, in RemoteCallback result);
     void removeActiveAdmin(in ComponentName policyReceiver);
     
     void setActivePasswordState(int mode, int length);
diff --git a/core/java/android/os/IRemoteCallback.aidl b/core/java/android/os/IRemoteCallback.aidl
new file mode 100644 (file)
index 0000000..f0c6c73
--- /dev/null
@@ -0,0 +1,25 @@
+/* //device/java/android/android/app/IActivityPendingResult.aidl
+**
+** Copyright 2010, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+
+package android.os;
+
+import android.os.Bundle;
+
+/** @hide */
+oneway interface IRemoteCallback {
+    void sendResult(in Bundle data);
+}
diff --git a/core/java/android/os/MailboxNotAvailableException.java b/core/java/android/os/MailboxNotAvailableException.java
deleted file mode 100644 (file)
index 574adbd..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.os;
-
-/** @hide */
-public class MailboxNotAvailableException extends Throwable
-{
-  /**
-   * This exception represents the case when a request for a
-   * named, published mailbox fails because the requested name has not been published
-   */
-
-    public
-    MailboxNotAvailableException()
-    {
-    }
-
-    public
-    MailboxNotAvailableException(String s)
-    {
-        super(s);
-    }
-}
index 1bc554e..ad55abd 100644 (file)
@@ -29,7 +29,7 @@ public final class Messenger implements Parcelable {
      * Create a new Messenger pointing to the given Handler.  Any Message
      * objects sent through this Messenger will appear in the Handler as if
      * {@link Handler#sendMessage(Message) Handler.sendMessage(Message)} had
-     * be called directly.
+     * been called directly.
      * 
      * @param target The Handler that will receive sent messages.
      */
diff --git a/core/java/android/os/RemoteCallback.aidl b/core/java/android/os/RemoteCallback.aidl
new file mode 100644 (file)
index 0000000..7ae56f5
--- /dev/null
@@ -0,0 +1,19 @@
+/*
+** Copyright 2010, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+
+package android.os;
+
+parcelable RemoteCallback;
diff --git a/core/java/android/os/RemoteCallback.java b/core/java/android/os/RemoteCallback.java
new file mode 100644 (file)
index 0000000..ca95bdf
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/**
+ * TODO: Make this a public API?  Let's see how it goes with a few use
+ * cases first.
+ * @hide
+ */
+public abstract class RemoteCallback implements Parcelable {
+    final Handler mHandler;
+    final IRemoteCallback mTarget;
+    
+    class DeliverResult implements Runnable {
+        final Bundle mResult;
+        
+        DeliverResult(Bundle result) {
+            mResult = result;
+        }
+        
+        public void run() {
+            onResult(mResult);
+        }
+    }
+    
+    class LocalCallback extends IRemoteCallback.Stub {
+        public void sendResult(Bundle bundle) {
+            mHandler.post(new DeliverResult(bundle));
+        }
+    }
+    
+    static class RemoteCallbackProxy extends RemoteCallback {
+        RemoteCallbackProxy(IRemoteCallback target) {
+            super(target);
+        }
+        
+        protected void onResult(Bundle bundle) {
+        }
+    }
+    
+    public RemoteCallback(Handler handler) {
+        mHandler = handler;
+        mTarget = new LocalCallback();
+    }
+    
+     RemoteCallback(IRemoteCallback target) {
+        mHandler = null;
+        mTarget = target;
+    }
+    
+    public void sendResult(Bundle bundle) throws RemoteException {
+        mTarget.sendResult(bundle);
+    }
+    
+    protected abstract void onResult(Bundle bundle);
+    
+    public boolean equals(Object otherObj) {
+        if (otherObj == null) {
+            return false;
+        }
+        try {
+            return mTarget.asBinder().equals(((RemoteCallback)otherObj)
+                    .mTarget.asBinder());
+        } catch (ClassCastException e) {
+        }
+        return false;
+    }
+    
+    public int hashCode() {
+        return mTarget.asBinder().hashCode();
+    }
+    
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeStrongBinder(mTarget.asBinder());
+    }
+
+    public static final Parcelable.Creator<RemoteCallback> CREATOR
+            = new Parcelable.Creator<RemoteCallback>() {
+        public RemoteCallback createFromParcel(Parcel in) {
+            IBinder target = in.readStrongBinder();
+            return target != null ? new RemoteCallbackProxy(
+                    IRemoteCallback.Stub.asInterface(target)) : null;
+        }
+
+        public RemoteCallback[] newArray(int size) {
+            return new RemoteCallback[size];
+        }
+    };
+}
index 31f71d3..259398f 100644 (file)
     <!-- Title of policy access to watch user login attempts -->
     <string name="policylab_watchLogin">Watch login attempts</string>
     <!-- Description of policy access to watch user login attempts -->
-    <string name="policydesc_watchLogin">Monitor attempts to login to
-        the device, in particular to respond to failed login attempts.</string>
+    <string name="policydesc_watchLogin">Monitor failed attempts to login to
+        the device, to perform some action.</string>
     <!-- Title of policy access to reset user's password -->
-    <string name="policylab_resetPassword">Reset your password</string>
+    <string name="policylab_resetPassword">Reset password</string>
     <!-- Description of policy access to reset user's password -->
     <string name="policydesc_resetPassword">Force your password
         to a new value, requiring the administrator give it to you
     <string name="policylab_forceLock">Force lock</string>
     <!-- Description of policy access to limiting the user's password choices -->
     <string name="policydesc_forceLock">Force the device to immediately lock,
-        requiring that its password is re-entered.</string>
+        requiring that you re-enter its password.</string>
     <!-- Title of policy access to wipe the user's data -->
     <string name="policylab_wipeData">Erase all data</string>
     <!-- Description of policy access to wipe the user's data -->
     <string name="policydesc_wipeData">Perform a factory reset, deleting
-        all of your data without any confirmation from you.</string>
+        all of your data without any confirmation.</string>
 
     <!-- The order of these is important, don't reorder without changing Contacts.java --> <skip />
     <!-- Phone number types from android.provider.Contacts. This could be used when adding a new phone number for a contact, for example. -->
index fbd5317..ebd6f3d 100644 (file)
@@ -23,19 +23,23 @@ import org.xmlpull.v1.XmlPullParser;
 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
     ActiveAdmin mActiveAdmin;
     
     static class ActiveAdmin {
+        final DeviceAdminInfo info;
+        
+        int passwordMode = DevicePolicyManager.PASSWORD_MODE_UNSPECIFIED;
+        int minimumPasswordLength = 0;
+        long maximumTimeToUnlock = 0;
+        int maximumFailedPasswordsForWipe = 0;
+        
         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;
+        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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
         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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
             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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
         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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
             // 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 class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
         }
     }
     
+    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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
         }
     }
     
+    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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
                 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
             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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
                 long ident = Binder.clearCallingIdentity();
                 try {
                     mFailedPasswordAttempts = 0;
+                    saveSettingsLocked();
                     sendAdminCommandLocked(DeviceAdmin.ACTION_PASSWORD_SUCCEEDED,
                             DeviceAdminInfo.USES_POLICY_WATCH_LOGIN);
                 } finally {