More device admin work: description, policy control.

There is now a description attribute associated with all components,
that can supply user-visible information about what the component does.
We use this to show such information about device admins, and wallpapers
are also updated to be able to show this in addition to the existing
description in their meta-data.

This also defines security control for admins, requiring that they
declare the policies they will touch, and enforcing that they do
so to be able to use various APIs.
diff --git a/api/current.xml b/api/current.xml
index 8cd368f..cd200c1 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -20148,6 +20148,34 @@
  visibility="public"
 >
 </method>
+<method name="getTagForPolicy"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="policyIdent" type="int">
+</parameter>
+</method>
+<method name="loadDescription"
+ return="java.lang.CharSequence"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="pm" type="android.content.pm.PackageManager">
+</parameter>
+<exception name="Resources.NotFoundException" type="android.content.res.Resources.NotFoundException">
+</exception>
+</method>
 <method name="loadIcon"
  return="android.graphics.drawable.Drawable"
  abstract="false"
@@ -20174,6 +20202,19 @@
 <parameter name="pm" type="android.content.pm.PackageManager">
 </parameter>
 </method>
+<method name="usesPolicy"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="policyIdent" type="int">
+</parameter>
+</method>
 <method name="writeToParcel"
  return="void"
  abstract="false"
@@ -20199,6 +20240,72 @@
  visibility="public"
 >
 </field>
+<field name="USES_POLICY_FORCE_LOCK"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="USES_POLICY_LIMIT_PASSWORD"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="USES_POLICY_LIMIT_UNLOCK"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="USES_POLICY_RESET_PASSWORD"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="USES_POLICY_WATCH_LOGIN"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="USES_POLICY_WIPE_DATA"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="5"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 </class>
 <class name="DevicePolicyManager"
  extends="java.lang.Object"
@@ -41567,6 +41674,16 @@
  visibility="public"
 >
 </field>
+<field name="descriptionRes"
+ type="int"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="enabled"
  type="boolean"
  transient="false"
diff --git a/core/java/android/app/DeviceAdmin.java b/core/java/android/app/DeviceAdmin.java
index 16832db..9750d6e 100644
--- a/core/java/android/app/DeviceAdmin.java
+++ b/core/java/android/app/DeviceAdmin.java
@@ -82,6 +82,10 @@
      * {@link DevicePolicyManager#getMinimumPasswordLength()
      * DevicePolicyManager.getMinimumPasswordLength()}.  You will generally
      * handle this in {@link DeviceAdmin#onPasswordChanged(Context, Intent)}.
+     * 
+     * <p>The calling device admin must have requested
+     * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to receive
+     * this broadcast.
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_PASSWORD_CHANGED
@@ -94,6 +98,10 @@
      * {@link DevicePolicyManager#getCurrentFailedPasswordAttempts()
      * DevicePolicyManager.getCurrentFailedPasswordAttempts()}.  You will generally
      * handle this in {@link DeviceAdmin#onPasswordFailed(Context, Intent)}.
+     * 
+     * <p>The calling device admin must have requested
+     * {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} to receive
+     * this broadcast.
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_PASSWORD_FAILED
@@ -102,6 +110,10 @@
     /**
      * Action sent to a device administrator when the user has successfully
      * entered their password, after failing one or more times.
+     * 
+     * <p>The calling device admin must have requested
+     * {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} to receive
+     * this broadcast.
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_PASSWORD_SUCCEEDED
diff --git a/core/java/android/app/DeviceAdminInfo.java b/core/java/android/app/DeviceAdminInfo.java
index eac6e46..92fdbc8 100644
--- a/core/java/android/app/DeviceAdminInfo.java
+++ b/core/java/android/app/DeviceAdminInfo.java
@@ -19,21 +19,28 @@
 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;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
+import android.content.res.Resources.NotFoundException;
 import android.graphics.drawable.Drawable;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.Printer;
+import android.util.SparseArray;
 import android.util.Xml;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
 
 /**
  * This class is used to specify meta information of a device administrator
@@ -43,11 +50,123 @@
     static final String TAG = "DeviceAdminInfo";
     
     /**
+     * A type of policy that this device admin can use: limit the passwords
+     * that the user can select, via {@link DevicePolicyManager#setPasswordMode}
+     * and {@link DevicePolicyManager#setMinimumPasswordLength}.
+     * 
+     * <p>To control this policy, the device admin must have a "limit-password"
+     * tag in the "uses-policies" section of its meta-data.
+     */
+    public static final int USES_POLICY_LIMIT_PASSWORD = 0;
+    
+    /**
+     * A type of policy that this device admin can use: able to watch login
+     * attempts from the user, via {@link DeviceAdmin#ACTION_PASSWORD_FAILED},
+     * {@link DeviceAdmin#ACTION_PASSWORD_SUCCEEDED}, and
+     * {@link DevicePolicyManager#getCurrentFailedPasswordAttempts}.
+     * 
+     * <p>To control this policy, the device admin must have a "watch-login"
+     * tag in the "uses-policies" section of its meta-data.
+     */
+    public static final int USES_POLICY_WATCH_LOGIN = 1;
+
+    /**
+     * A type of policy that this device admin can use: able to reset the
+     * user's password via
+     * {@link DevicePolicyManager#resetPassword}.
+     * 
+     * <p>To control this policy, the device admin must have a "reset-password"
+     * tag in the "uses-policies" section of its meta-data.
+     */
+    public static final int USES_POLICY_RESET_PASSWORD = 2;
+
+    /**
+     * A type of policy that this device admin can use: able to limit the
+     * maximum lock timeout for the device via
+     * {@link DevicePolicyManager#setMaximumTimeToLock}.
+     * 
+     * <p>To control this policy, the device admin must have a "limit-unlock"
+     * tag in the "uses-policies" section of its meta-data.
+     */
+    public static final int USES_POLICY_LIMIT_UNLOCK = 3;
+
+    /**
+     * A type of policy that this device admin can use: able to force the device
+     * to lock via{@link DevicePolicyManager#lockNow}.
+     * 
+     * <p>To control this policy, the device admin must have a "force-lock"
+     * tag in the "uses-policies" section of its meta-data.
+     */
+    public static final int USES_POLICY_FORCE_LOCK = 4;
+
+    /**
+     * A type of policy that this device admin can use: able to factory
+     * reset the device, erasing all of the user's data, via
+     * {@link DevicePolicyManager#wipeData}.
+     * 
+     * <p>To control this policy, the device admin must have a "wipe-data"
+     * tag in the "uses-policies" section of its meta-data.
+     */
+    public static final int USES_POLICY_WIPE_DATA = 5;
+
+    /** @hide */
+    public static class PolicyInfo {
+        final public String tag;
+        final public int label;
+        final public int description;
+        
+        public PolicyInfo(String tagIn, int labelIn, int descriptionIn) {
+            tag = tagIn;
+            label = labelIn;
+            description = descriptionIn;
+        }
+    }
+    
+    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));
+        }
+    }
+    
+    /**
      * The BroadcastReceiver that implements this device admin component.
      */
     final ResolveInfo mReceiver;
     
     /**
+     * The policies this administrator needs access to.
+     */
+    int mUsesPolicies;
+    
+    /**
      * Constructor.
      * 
      * @param context The Context in which we are parsing the device admin.
@@ -86,6 +205,32 @@
                     com.android.internal.R.styleable.Wallpaper);
 
             sa.recycle();
+            
+            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;
+                }
+                String tagName = parser.getName();
+                if (tagName.equals("uses-policies")) {
+                    int innerDepth = parser.getDepth();
+                    while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+                           && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
+                        if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                            continue;
+                        }
+                        String policyName = parser.getName();
+                        Integer val = sKnownPolicies.get(policyName);
+                        if (val != null) {
+                            mUsesPolicies |= 1 << val.intValue();
+                        } else {
+                            Log.w(TAG, "Unknown tag under uses-policies of "
+                                    + getComponent() + ": " + policyName);
+                        }
+                    }
+                }
+            }
         } finally {
             if (parser != null) parser.close();
         }
@@ -93,6 +238,7 @@
 
     DeviceAdminInfo(Parcel source) {
         mReceiver = ResolveInfo.CREATOR.createFromParcel(source);
+        mUsesPolicies = source.readInt();
     }
     
     /**
@@ -137,6 +283,26 @@
     }
     
     /**
+     * Load user-visible description associated with this device admin.
+     * 
+     * @param pm Supply a PackageManager used to load the device admin's
+     * resources.
+     */
+    public CharSequence loadDescription(PackageManager pm) throws NotFoundException {
+        if (mReceiver.activityInfo.descriptionRes != 0) {
+            String packageName = mReceiver.resolvePackageName;
+            ApplicationInfo applicationInfo = null;
+            if (packageName == null) {
+                packageName = mReceiver.activityInfo.packageName;
+                applicationInfo = mReceiver.activityInfo.applicationInfo;
+            }
+            return pm.getText(packageName,
+                    mReceiver.activityInfo.descriptionRes, applicationInfo);
+        }
+        throw new NotFoundException();
+    }
+    
+    /**
      * Load the user-displayed icon for this device admin.
      * 
      * @param pm Supply a PackageManager used to load the device admin's
@@ -146,6 +312,38 @@
         return mReceiver.loadIcon(pm);
     }
     
+    /**
+     * Return true if the device admin has requested that it be able to use
+     * the given policy control.  The possible policy identifier inputs are:
+     * {@link #USES_POLICY_LIMIT_PASSWORD}, {@link #USES_POLICY_WATCH_LOGIN},
+     * {@link #USES_POLICY_RESET_PASSWORD}, {@link #USES_POLICY_LIMIT_UNLOCK},
+     * {@link #USES_POLICY_FORCE_LOCK}, {@link #USES_POLICY_WIPE_DATA}.
+     */
+    public boolean usesPolicy(int policyIdent) {
+        return (mUsesPolicies & (1<<policyIdent)) != 0;
+    }
+    
+    /**
+     * Return the XML tag name for the given policy identifier.  Valid identifiers
+     * are as per {@link #usesPolicy(int)}.  If the given identifier is not
+     * known, null is returned.
+     */
+    public String getTagForPolicy(int policyIdent) {
+        return sRevKnownPolicies.get(policyIdent).tag;
+    }
+    
+    /** @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));
+            }
+        }
+        return res;
+    }
+    
     public void dump(Printer pw, String prefix) {
         pw.println(prefix + "Receiver:");
         mReceiver.dump(pw, prefix + "  ");
@@ -164,6 +362,7 @@
      */
     public void writeToParcel(Parcel dest, int flags) {
         mReceiver.writeToParcel(dest, flags);
+        dest.writeInt(mUsesPolicies);
     }
 
     /**
diff --git a/core/java/android/app/DevicePolicyManager.java b/core/java/android/app/DevicePolicyManager.java
index 538ba5b..25e3230 100644
--- a/core/java/android/app/DevicePolicyManager.java
+++ b/core/java/android/app/DevicePolicyManager.java
@@ -163,6 +163,10 @@
      * the user's preference, and any other considerations) is the one that
      * is in effect.
      * 
+     * <p>The calling device admin must have requested
+     * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call
+     * this method; if it has not, a security exception will be thrown.
+     * 
      * @param admin Which {@link DeviceAdmin} this request is associated with.
      * @param mode The new desired mode.  One of
      * {@link #PASSWORD_MODE_UNSPECIFIED}, {@link #PASSWORD_MODE_SOMETHING},
@@ -205,6 +209,10 @@
      * {@link #PASSWORD_MODE_NUMERIC} or {@link #PASSWORD_MODE_ALPHANUMERIC}
      * with {@link #setPasswordMode}.
      * 
+     * <p>The calling device admin must have requested
+     * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call
+     * this method; if it has not, a security exception will be thrown.
+     * 
      * @param admin Which {@link DeviceAdmin} this request is associated with.
      * @param length The new desired minimum password length.  A value of 0
      * means there is no restriction.
@@ -239,6 +247,10 @@
      * to meet the policy requirements (mode, minimum length) that have been
      * requested.
      * 
+     * <p>The calling device admin must have requested
+     * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call
+     * this method; if it has not, a security exception will be thrown.
+     * 
      * @return Returns true if the password meets the current requirements,
      * else false.
      */
@@ -256,6 +268,10 @@
     /**
      * Retrieve the number of times the user has failed at entering a
      * password since that last successful password entry.
+     * 
+     * <p>The calling device admin must have requested
+     * {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} to be able to call
+     * this method; if it has not, a security exception will be thrown.
      */
     public int getCurrentFailedPasswordAttempts() {
         if (mService != null) {
@@ -277,6 +293,10 @@
      * if it contains only digits, that is still an acceptable alphanumeric
      * password.)
      * 
+     * <p>The calling device admin must have requested
+     * {@link DeviceAdminInfo#USES_POLICY_RESET_PASSWORD} to be able to call
+     * this method; if it has not, a security exception will be thrown.
+     * 
      * @param password The new password for the user.
      * @return Returns true if the password was applied, or false if it is
      * not acceptable for the current constraints.
@@ -297,6 +317,10 @@
      * maximum time for user activity until the device will lock.  This limits
      * the length that the user can set.  It takes effect immediately.
      * 
+     * <p>The calling device admin must have requested
+     * {@link DeviceAdminInfo#USES_POLICY_LIMIT_UNLOCK} to be able to call
+     * this method; if it has not, a security exception will be thrown.
+     * 
      * @param admin Which {@link DeviceAdmin} this request is associated with.
      * @param timeMs The new desired maximum time to lock in milliseconds.
      * A value of 0 means there is no restriction.
@@ -329,6 +353,10 @@
     /**
      * Make the device lock immediately, as if the lock screen timeout has
      * expired at the point of this call.
+     * 
+     * <p>The calling device admin must have requested
+     * {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK} to be able to call
+     * this method; if it has not, a security exception will be thrown.
      */
     public void lockNow() {
         if (mService != null) {
@@ -345,6 +373,10 @@
      * erasing all user data while next booting up.  External storage such
      * as SD cards will not be erased.
      * 
+     * <p>The calling device admin must have requested
+     * {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} to be able to call
+     * this method; if it has not, a security exception will be thrown.
+     * 
      * @param flags Bit mask of additional options: currently must be 0.
      */
     public void wipeData(int flags) {
diff --git a/core/java/android/app/WallpaperInfo.java b/core/java/android/app/WallpaperInfo.java
index 1612ac9..5ca3fb54 100644
--- a/core/java/android/app/WallpaperInfo.java
+++ b/core/java/android/app/WallpaperInfo.java
@@ -21,6 +21,7 @@
 
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
@@ -204,7 +205,7 @@
 
         return pm.getDrawable(mService.serviceInfo.packageName,
                               mThumbnailResource,
-                              null);
+                              mService.serviceInfo.applicationInfo);
     }
 
     /**
@@ -212,25 +213,33 @@
      */
     public CharSequence loadAuthor(PackageManager pm) throws NotFoundException {
         if (mAuthorResource <= 0) throw new NotFoundException();
-        return pm.getText(
-            (mService.resolvePackageName != null)
-                ? mService.resolvePackageName
-                : getPackageName(),
-            mAuthorResource,
-            null);
+        String packageName = mService.resolvePackageName;
+        ApplicationInfo applicationInfo = null;
+        if (packageName == null) {
+            packageName = mService.serviceInfo.packageName;
+            applicationInfo = mService.serviceInfo.applicationInfo;
+        }
+        return pm.getText(packageName, mAuthorResource, applicationInfo);
     }
 
     /**
      * Return a brief summary of this wallpaper's behavior.
      */
     public CharSequence loadDescription(PackageManager pm) throws NotFoundException {
+        String packageName = mService.resolvePackageName;
+        ApplicationInfo applicationInfo = null;
+        if (packageName == null) {
+            packageName = mService.serviceInfo.packageName;
+            applicationInfo = mService.serviceInfo.applicationInfo;
+        }
+        if (mService.serviceInfo.descriptionRes != 0) {
+            return pm.getText(packageName, mService.serviceInfo.descriptionRes,
+                    applicationInfo);
+            
+        }
         if (mDescriptionResource <= 0) throw new NotFoundException();
-        return pm.getText(
-            (mService.resolvePackageName != null)
-                ? mService.resolvePackageName
-                : getPackageName(),
-            mDescriptionResource,
-            null);
+        return pm.getText(packageName, mDescriptionResource,
+                mService.serviceInfo.applicationInfo);
     }
     
     /**
diff --git a/core/java/android/content/pm/ComponentInfo.java b/core/java/android/content/pm/ComponentInfo.java
index 73c9244..338c62b6 100644
--- a/core/java/android/content/pm/ComponentInfo.java
+++ b/core/java/android/content/pm/ComponentInfo.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2008 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.content.pm;
 
 import android.graphics.drawable.Drawable;
@@ -27,6 +43,13 @@
     public String processName;
 
     /**
+     * A string resource identifier (in the package's resources) containing
+     * a user-readable description of the component.  From the "description"
+     * attribute or, if not set, 0.
+     */
+    public int descriptionRes;
+    
+    /**
      * Indicates whether or not this component may be instantiated.  Note that this value can be
      * overriden by the one in its parent {@link ApplicationInfo}.
      */
@@ -47,6 +70,7 @@
         super(orig);
         applicationInfo = orig.applicationInfo;
         processName = orig.processName;
+        descriptionRes = orig.descriptionRes;
         enabled = orig.enabled;
         exported = orig.exported;
     }
@@ -108,6 +132,9 @@
         super.dumpFront(pw, prefix);
         pw.println(prefix + "enabled=" + enabled + " exported=" + exported
                 + " processName=" + processName);
+        if (descriptionRes != 0) {
+            pw.println(prefix + "description=" + descriptionRes);
+        }
     }
     
     protected void dumpBack(Printer pw, String prefix) {
@@ -124,6 +151,7 @@
         super.writeToParcel(dest, parcelableFlags);
         applicationInfo.writeToParcel(dest, parcelableFlags);
         dest.writeString(processName);
+        dest.writeInt(descriptionRes);
         dest.writeInt(enabled ? 1 : 0);
         dest.writeInt(exported ? 1 : 0);
     }
@@ -132,6 +160,7 @@
         super(source);
         applicationInfo = ApplicationInfo.CREATOR.createFromParcel(source);
         processName = source.readString();
+        descriptionRes = source.readInt();
         enabled = (source.readInt() != 0);
         exported = (source.readInt() != 0);
     }
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 8a5df32..bbde0a6 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -119,15 +119,18 @@
     static class ParseComponentArgs extends ParsePackageItemArgs {
         final String[] sepProcesses;
         final int processRes;
+        final int descriptionRes;
         final int enabledRes;
         int flags;
         
         ParseComponentArgs(Package _owner, String[] _outError,
                 int _nameRes, int _labelRes, int _iconRes,
-                String[] _sepProcesses, int _processRes,int _enabledRes) {
+                String[] _sepProcesses, int _processRes,
+                int _descriptionRes, int _enabledRes) {
             super(_owner, _outError, _nameRes, _labelRes, _iconRes);
             sepProcesses = _sepProcesses;
             processRes = _processRes;
+            descriptionRes = _descriptionRes;
             enabledRes = _enabledRes;
         }
     }
@@ -1602,6 +1605,7 @@
                     com.android.internal.R.styleable.AndroidManifestActivity_icon,
                     mSeparateProcesses,
                     com.android.internal.R.styleable.AndroidManifestActivity_process,
+                    com.android.internal.R.styleable.AndroidManifestActivity_description,
                     com.android.internal.R.styleable.AndroidManifestActivity_enabled);
         }
         
@@ -1803,6 +1807,7 @@
                     com.android.internal.R.styleable.AndroidManifestActivityAlias_icon,
                     mSeparateProcesses,
                     0,
+                    com.android.internal.R.styleable.AndroidManifestActivityAlias_description,
                     com.android.internal.R.styleable.AndroidManifestActivityAlias_enabled);
             mParseActivityAliasArgs.tag = "<activity-alias>";
         }
@@ -1837,6 +1842,9 @@
         info.nonLocalizedLabel = target.info.nonLocalizedLabel;
         info.launchMode = target.info.launchMode;
         info.processName = target.info.processName;
+        if (info.descriptionRes == 0) {
+            info.descriptionRes = target.info.descriptionRes;
+        }
         info.screenOrientation = target.info.screenOrientation;
         info.taskAffinity = target.info.taskAffinity;
         info.theme = target.info.theme;
@@ -1926,6 +1934,7 @@
                     com.android.internal.R.styleable.AndroidManifestProvider_icon,
                     mSeparateProcesses,
                     com.android.internal.R.styleable.AndroidManifestProvider_process,
+                    com.android.internal.R.styleable.AndroidManifestProvider_description,
                     com.android.internal.R.styleable.AndroidManifestProvider_enabled);
             mParseProviderArgs.tag = "<provider>";
         }
@@ -2188,6 +2197,7 @@
                     com.android.internal.R.styleable.AndroidManifestService_icon,
                     mSeparateProcesses,
                     com.android.internal.R.styleable.AndroidManifestService_process,
+                    com.android.internal.R.styleable.AndroidManifestService_description,
                     com.android.internal.R.styleable.AndroidManifestService_enabled);
             mParseServiceArgs.tag = "<service>";
         }
@@ -2640,6 +2650,11 @@
                         owner.applicationInfo.processName, args.sa.getNonResourceString(args.processRes),
                         args.flags, args.sepProcesses, args.outError);
             }
+            
+            if (args.descriptionRes != 0) {
+                outInfo.descriptionRes = args.sa.getResourceId(args.descriptionRes, 0);
+            }
+            
             outInfo.enabled = args.sa.getBoolean(args.enabledRes, true);
         }
 
diff --git a/core/java/android/widget/AppSecurityPermissions.java b/core/java/android/widget/AppSecurityPermissions.java
index a09f23c..aa14c81 100755
--- a/core/java/android/widget/AppSecurityPermissions.java
+++ b/core/java/android/widget/AppSecurityPermissions.java
@@ -146,6 +146,19 @@
         }
     }
     
+    /**
+     * Utility to retrieve a view displaying a single permission.
+     */
+    public static View getPermissionItemView(Context context,
+            CharSequence grpName, CharSequence description, boolean dangerous) {
+        LayoutInflater inflater = (LayoutInflater)context.getSystemService(
+                Context.LAYOUT_INFLATER_SERVICE);
+        Drawable icon = context.getResources().getDrawable(dangerous
+                ? R.drawable.ic_bullet_key_permission : R.drawable.ic_text_dot);
+        return getPermissionItemView(context, inflater, grpName,
+                description, dangerous, icon);
+    }
+    
     private void getAllUsedPermissions(int sharedUid, Set<PermissionInfo> permSet) {
         String sharedPkgList[] = mPm.getPackagesForUid(sharedUid);
         if(sharedPkgList == null || (sharedPkgList.length == 0)) {
@@ -304,15 +317,20 @@
         mNoPermsView.setVisibility(View.VISIBLE);
     }
 
-    private View getPermissionItemView(CharSequence grpName, String permList,
+    private View getPermissionItemView(CharSequence grpName, CharSequence permList,
             boolean dangerous) {
-        View permView = mInflater.inflate(R.layout.app_permission_item, null);
-        Drawable icon = dangerous ? mDangerousIcon : mNormalIcon;
+        return getPermissionItemView(mContext, mInflater, grpName, permList,
+                dangerous, dangerous ? mDangerousIcon : mNormalIcon);
+    }
+
+    private static View getPermissionItemView(Context context, LayoutInflater inflater,
+            CharSequence grpName, CharSequence permList, boolean dangerous, Drawable icon) {
+        View permView = inflater.inflate(R.layout.app_permission_item, null);
 
         TextView permGrpView = (TextView) permView.findViewById(R.id.permission_group);
         TextView permDescView = (TextView) permView.findViewById(R.id.permission_list);
         if (dangerous) {
-            final Resources resources = mContext.getResources();
+            final Resources resources = context.getResources();
             permGrpView.setTextColor(resources.getColor(R.color.perms_dangerous_grp_color));
             permDescView.setTextColor(resources.getColor(R.color.perms_dangerous_perm_color));
         }
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index c6ef3a0..7728c50 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -938,6 +938,7 @@
             is a period then it is appended to your package name. -->
         <attr name="name" />
         <attr name="label" />
+        <attr name="description" />
         <attr name="icon" />
         <attr name="process" />
         <attr name="authorities" />
@@ -1016,6 +1017,7 @@
             is a period then it is appended to your package name. -->
         <attr name="name" />
         <attr name="label" />
+        <attr name="description" />
         <attr name="icon" />
         <attr name="permission" />
         <attr name="process" />
@@ -1047,6 +1049,7 @@
             is a period then it is appended to your package name. -->
         <attr name="name" />
         <attr name="label" />
+        <attr name="description" />
         <attr name="icon" />
         <attr name="permission" />
         <attr name="process" />
@@ -1078,6 +1081,7 @@
         <attr name="name" />
         <attr name="theme" />
         <attr name="label" />
+        <attr name="description" />
         <attr name="icon" />
         <attr name="launchMode" />
         <attr name="screenOrientation" />
@@ -1130,6 +1134,7 @@
              "com.mycompany.MyName". -->  
         <attr name="targetActivity" format="string" />
         <attr name="label" />
+        <attr name="description" />
         <attr name="icon" />
         <attr name="permission" />
         <!-- Specify whether the activity-alias is enabled or not (that is, can be instantiated by the system).
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 265dacd..0ef07ff 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1157,6 +1157,40 @@
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_cache_filesystem">Allows an application to read and write the cache filesystem.</string>
 
+    <!-- Policy administration -->
+
+    <!-- Title of policy access to limiting the user's password choices -->
+    <string name="policylab_limitPassword">Limit password</string>
+    <!-- Description of policy access to limiting the user's password choices -->
+    <string name="policydesc_limitPassword">Restrict the types of passwords you
+        are allowed to use.</string>
+    <!-- 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>
+    <!-- Title of policy access to reset user's password -->
+    <string name="policylab_resetPassword">Reset your 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
+        before you can log in.</string>
+    <!-- Title of policy access to limiting the user's unlock timeout -->
+    <string name="policylab_limitUnlock">Limit lock timeout</string>
+    <!-- Description of policy access to limiting the user's unlock timeout -->
+    <string name="policydesc_limitUnlock">Restrict the unlock timeout
+        durations you can select.</string>
+    <!-- Title of policy access to force lock the device -->
+    <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>
+    <!-- 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>
+
     <!-- 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. -->
     <string-array name="phoneTypes">
diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java
index 36da4eb..fbd5317 100644
--- a/services/java/com/android/server/DevicePolicyManagerService.java
+++ b/services/java/com/android/server/DevicePolicyManagerService.java
@@ -91,7 +91,8 @@
         return mIPowerManager;
     }
     
-    ActiveAdmin getActiveAdminForCallerLocked(ComponentName who) throws SecurityException {
+    ActiveAdmin getActiveAdminForCallerLocked(ComponentName who, int reqPolicy)
+            throws SecurityException {
         if (mActiveAdmin != null && mActiveAdmin.getUid() == Binder.getCallingUid()) {
             if (who != null) {
                 if (!who.getPackageName().equals(mActiveAdmin.info.getActivityInfo().packageName)
@@ -99,20 +100,27 @@
                     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());
     }
     
-    
-    void sendAdminCommandLocked(ActiveAdmin policy, String action) {
+    void sendAdminCommandLocked(ActiveAdmin admin, String action) {
         Intent intent = new Intent(action);
-        intent.setComponent(policy.info.getComponent());
+        intent.setComponent(admin.info.getComponent());
         mContext.sendBroadcast(intent);
     }
     
-    void sendAdminCommandLocked(String action) {
+    void sendAdminCommandLocked(String action, int reqPolicy) {
         if (mActiveAdmin != null) {
+            if (mActiveAdmin.info.usesPolicy(reqPolicy)) {
+                return;
+            }
             sendAdminCommandLocked(mActiveAdmin, action);
         }
     }
@@ -353,7 +361,8 @@
             if (who == null) {
                 throw new NullPointerException("ComponentName is null");
             }
-            ActiveAdmin ap = getActiveAdminForCallerLocked(who);
+            ActiveAdmin ap = getActiveAdminForCallerLocked(who,
+                    DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
             if (ap.passwordMode != mode) {
                 ap.passwordMode = mode;
                 saveSettingsLocked();
@@ -373,7 +382,8 @@
             if (who == null) {
                 throw new NullPointerException("ComponentName is null");
             }
-            ActiveAdmin ap = getActiveAdminForCallerLocked(who);
+            ActiveAdmin ap = getActiveAdminForCallerLocked(who,
+                    DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
             if (ap.minimumPasswordLength != length) {
                 ap.minimumPasswordLength = length;
                 saveSettingsLocked();
@@ -391,7 +401,8 @@
         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);
+            getActiveAdminForCallerLocked(null,
+                    DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
             return mActivePasswordMode >= getPasswordMode()
                     && mActivePasswordLength >= getMinimumPasswordLength();
         }
@@ -401,7 +412,8 @@
         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);
+            getActiveAdminForCallerLocked(null,
+                    DeviceAdminInfo.USES_POLICY_WATCH_LOGIN);
             return mFailedPasswordAttempts;
         }
     }
@@ -411,7 +423,8 @@
         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);
+            getActiveAdminForCallerLocked(null,
+                    DeviceAdminInfo.USES_POLICY_RESET_PASSWORD);
             mode = getPasswordMode();
             if (password.length() < getMinimumPasswordLength()) {
                 return false;
@@ -436,7 +449,8 @@
             if (who == null) {
                 throw new NullPointerException("ComponentName is null");
             }
-            ActiveAdmin ap = getActiveAdminForCallerLocked(who);
+            ActiveAdmin ap = getActiveAdminForCallerLocked(who,
+                    DeviceAdminInfo.USES_POLICY_LIMIT_UNLOCK);
             if (ap.maximumTimeToUnlock != timeMs) {
                 ap.maximumTimeToUnlock = timeMs;
                 
@@ -468,7 +482,8 @@
         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);
+            getActiveAdminForCallerLocked(null,
+                    DeviceAdminInfo.USES_POLICY_FORCE_LOCK);
             // STOPSHIP need to implement.
         }
     }
@@ -477,7 +492,8 @@
         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);
+            getActiveAdminForCallerLocked(null,
+                    DeviceAdminInfo.USES_POLICY_WIPE_DATA);
         }
         long ident = Binder.clearCallingIdentity();
         try {
@@ -501,7 +517,8 @@
                     mActivePasswordMode = mode;
                     mActivePasswordLength = length;
                     mFailedPasswordAttempts = 0;
-                    sendAdminCommandLocked(DeviceAdmin.ACTION_PASSWORD_CHANGED);
+                    sendAdminCommandLocked(DeviceAdmin.ACTION_PASSWORD_CHANGED,
+                            DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
                 } finally {
                     Binder.restoreCallingIdentity(ident);
                 }
@@ -517,7 +534,8 @@
             long ident = Binder.clearCallingIdentity();
             try {
                 mFailedPasswordAttempts++;
-                sendAdminCommandLocked(DeviceAdmin.ACTION_PASSWORD_FAILED);
+                sendAdminCommandLocked(DeviceAdmin.ACTION_PASSWORD_FAILED,
+                        DeviceAdminInfo.USES_POLICY_WATCH_LOGIN);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -533,7 +551,8 @@
                 long ident = Binder.clearCallingIdentity();
                 try {
                     mFailedPasswordAttempts = 0;
-                    sendAdminCommandLocked(DeviceAdmin.ACTION_PASSWORD_SUCCEEDED);
+                    sendAdminCommandLocked(DeviceAdmin.ACTION_PASSWORD_SUCCEEDED,
+                            DeviceAdminInfo.USES_POLICY_WATCH_LOGIN);
                 } finally {
                     Binder.restoreCallingIdentity(ident);
                 }