Add password expiration support to DevicePolicyManager.

Change-Id: Ib2629ec547c123ac489d7f4cbd4e0a1d4aa07620
diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
index 2237c82..2bb0e33 100644
--- a/core/java/android/app/admin/DeviceAdminInfo.java
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -112,6 +112,15 @@
      */
     public static final int USES_POLICY_SETS_GLOBAL_PROXY = 5;
 
+    /**
+     * A type of policy that this device admin can use: force the user to
+     * change their password after an administrator-defined time limit.
+     *
+     * <p>To control this policy, the device admin must have an "expire-password"
+     * tag in the "uses-policies" section of its meta-data.
+     */
+    public static final int USES_POLICY_EXPIRE_PASSWORD = 6;
+
     /** @hide */
     public static class PolicyInfo {
         public final int ident;
@@ -150,7 +159,10 @@
         sPoliciesDisplayOrder.add(new PolicyInfo(USES_POLICY_SETS_GLOBAL_PROXY, "set-global-proxy",
                 com.android.internal.R.string.policylab_setGlobalProxy,
                 com.android.internal.R.string.policydesc_setGlobalProxy));
-        
+        sPoliciesDisplayOrder.add(new PolicyInfo(USES_POLICY_EXPIRE_PASSWORD, "expire-password",
+                com.android.internal.R.string.policylab_expirePassword,
+                com.android.internal.R.string.policydesc_expirePassword));
+
         for (int i=0; i<sPoliciesDisplayOrder.size(); i++) {
             PolicyInfo pi = sPoliciesDisplayOrder.get(i);
             sRevKnownPolicies.put(pi.ident, pi);
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index b4dd9e7..eccd7c9 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -145,7 +145,19 @@
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_PASSWORD_SUCCEEDED
             = "android.app.action.ACTION_PASSWORD_SUCCEEDED";
-    
+
+    /**
+     * Action periodically sent to a device administrator when the device password
+     * is expiring. 
+     *
+     * <p>The calling device admin must have requested
+     * {@link DeviceAdminInfo#USES_POLICY_EXPIRE_PASSWORD} to receive
+     * this broadcast.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_PASSWORD_EXPIRING
+            = "android.app.action.ACTION_PASSWORD_EXPIRING";
+
     /**
      * Name under which an DevicePolicy component publishes information
      * about itself.  This meta-data must reference an XML resource containing
@@ -251,7 +263,28 @@
      */
     public void onPasswordSucceeded(Context context, Intent intent) {
     }
-    
+
+    /**
+     * Called periodically when the password is about to expire or has expired.  It will typically
+     * be called on device boot, once per day before the password expires and at the time when it
+     * expires.
+     *
+     * <p>If the password is not updated by the user, this method will continue to be called
+     * once per day until the password is changed or the device admin disables password expiration.
+     *
+     * <p>The admin will typically post a notification requesting the user to change their password
+     * in response to this call. The actual password expiration time can be obtained by calling
+     * {@link DevicePolicyManager#getPasswordExpiration(ComponentName) }
+     *
+     * <p>The admin should be sure to take down any notifications it posted in response to this call
+     * when it receives {@link DeviceAdminReceiver#onPasswordChanged(Context, Intent) }.
+     *
+     * @param context The running context as per {@link #onReceive}.
+     * @param intent The received intent as per {@link #onReceive}.
+     */
+    public void onPasswordExpiring(Context context, Intent intent) {
+    }
+
     /**
      * Intercept standard device administrator broadcasts.  Implementations
      * should not override this method; it is better to implement the
@@ -276,6 +309,8 @@
             }
         } else if (ACTION_DEVICE_ADMIN_DISABLED.equals(action)) {
             onDisabled(context, intent);
+        } else if (ACTION_PASSWORD_EXPIRING.equals(action)) {
+            onPasswordExpiring(context, intent);
         }
     }
 }
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index ca27010..a18fdac 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -681,6 +681,73 @@
     }
 
     /**
+     * Called by a device admin to set the password expiration timeout. Calling this method
+     * will restart the countdown for password expiration for the given admin, as will changing
+     * the device password (for all admins).
+     *
+     * <p>The provided timeout is the time delta in ms and will be added to the current time.
+     * For example, to have the password expire 5 days from now, timeout would be
+     * 5 * 86400 * 1000 = 432000000 ms for timeout.
+     *
+     * <p>To disable password expiration, a value of 0 may be used for timeout.
+     *
+     * <p>Timeout must be at least 1 day or IllegalArgumentException will be thrown.
+     *
+     * <p>The calling device admin must have requested
+     * {@link DeviceAdminInfo#USES_POLICY_EXPIRE_PASSWORD} to be able to call this
+     * method; if it has not, a security exception will be thrown.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param timeout The limit (in ms) that a password can remain in effect. A value of 0
+     *        means there is no restriction (unlimited).
+     */
+    public void setPasswordExpirationTimeout(ComponentName admin, long timeout) {
+        if (mService != null) {
+            try {
+                mService.setPasswordExpirationTimeout(admin, timeout);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed talking with device policy service", e);
+            }
+        }
+    }
+
+    /**
+     * Get the current password expiration timeout for the given admin or the aggregate
+     * of all admins if admin is null.
+     *
+     * @param admin The name of the admin component to check, or null to aggregate all admins.
+     * @return The timeout for the given admin or the minimum of all timeouts
+     */
+    public long getPasswordExpirationTimeout(ComponentName admin) {
+        if (mService != null) {
+            try {
+                return mService.getPasswordExpirationTimeout(admin);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed talking with device policy service", e);
+            }
+        }
+        return 0;
+    }
+
+    /**
+     * Get the current password expiration time for the given admin or an aggregate of
+     * all admins if admin is null.
+     *
+     * @param admin The name of the admin component to check, or null to aggregate all admins.
+     * @return The password expiration time, in ms.
+     */
+    public long getPasswordExpiration(ComponentName admin) {
+        if (mService != null) {
+            try {
+                return mService.getPasswordExpiration(admin);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed talking with device policy service", e);
+            }
+        }
+        return 0;
+    }
+
+    /**
      * Retrieve the current password history length for all admins
      * or a particular one.
      * @param admin The name of the admin component to check, or null to aggregate
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 3fcd6fc..7acc83e 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -52,6 +52,11 @@
     void setPasswordHistoryLength(in ComponentName who, int length);
     int getPasswordHistoryLength(in ComponentName who);
 
+    void setPasswordExpirationTimeout(in ComponentName who, long expiration);
+    long getPasswordExpirationTimeout(in ComponentName who);
+
+    long getPasswordExpiration(in ComponentName who);
+
     boolean isActivePasswordSufficient();
     int getCurrentFailedPasswordAttempts();
     
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 3b01890..64cd00a 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1396,6 +1396,11 @@
     <string name="policydesc_setGlobalProxy">Set the device global proxy
         to be used while policy is enabled. Only the first device admin
         sets the effective global proxy.</string>
+    <!-- Title of policy access to enforce password expiration [CHAR LIMIT=30]-->
+    <string name="policylab_expirePassword">Set password expiration</string>
+    <!-- Description of policy access to enforce password expiration [CHAR LIMIT=110]-->
+    <string name="policydesc_expirePassword">Control how long before lockscreen password needs to be
+    changed</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. -->