Add password expiration test to DeviceAdminSample.

Change-Id: Ib5b398dc068227529d83be2b55c33225f9bbce80
diff --git a/samples/ApiDemos/res/layout/device_admin_sample.xml b/samples/ApiDemos/res/layout/device_admin_sample.xml
index 9c2f323..23f065a 100644
--- a/samples/ApiDemos/res/layout/device_admin_sample.xml
+++ b/samples/ApiDemos/res/layout/device_admin_sample.xml
@@ -143,6 +143,24 @@
         <LinearLayout android:orientation="horizontal" android:gravity="center"
             android:layout_width="match_parent" android:layout_height="wrap_content">
 
+            <EditText android:id="@+id/password_expiration"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:hint="@string/password_expiration_hint"
+                android:inputType="number">
+            </EditText>
+
+            <Button android:id="@+id/update_expiration_button"
+                android:layout_width="wrap_content" android:layout_height="wrap_content"
+                android_layout_gravity="east|center_vertical"
+                android:text="@string/update_expiration_label">
+            </Button>
+
+        </LinearLayout>
+
+        <LinearLayout android:orientation="horizontal" android:gravity="center"
+            android:layout_width="match_parent" android:layout_height="wrap_content">
+
             <EditText android:id="@+id/password"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
diff --git a/samples/ApiDemos/res/values/strings.xml b/samples/ApiDemos/res/values/strings.xml
index 93f145d..eb50834 100644
--- a/samples/ApiDemos/res/values/strings.xml
+++ b/samples/ApiDemos/res/values/strings.xml
@@ -49,7 +49,7 @@
         orientation modes.  Often you want to set the desired mode in your manifest
         instead of programmatically.</string>
     <string name="screen_orientation">Screen Orientation</string>
-    
+
     <string name="activity_translucent">App/Activity/Translucent</string>
     <string name="translucent_background">Example of how you can make an
             activity have a translucent background, compositing over
@@ -76,7 +76,7 @@
         can resize to adjust for the IME.</string>
     <string name="soft_input_modes_initial_text">Text editor.\n\nTap to show the IME,
         which will cause this window to resize as requested.</string>
-    
+
     <string name="activity_persistent">App/Activity/Persistent State</string>
     <string name="persistent_msg">Demonstration of persistent activity state with getPreferences(0).edit() and getPreferences(0).</string>
 
@@ -540,6 +540,8 @@
     <string name="password_minimum_numeric_hint">Minimum Numeric</string>
     <string name="password_minimum_nonletter_hint">Minimum Non-Letter</string>
     <string name="password_history_length_hint">Password History Length</string>
+    <string name="password_expiration_hint">Password Expiration Timeout (minutes) </string>
+    <string name="update_expiration_label">Update</string>
     <string name="set_password">Set Password</string>
     <string name="password_hint">Password</string>
     <string name="reset_password">Reset Password</string>
diff --git a/samples/ApiDemos/res/xml/device_admin_sample.xml b/samples/ApiDemos/res/xml/device_admin_sample.xml
index 7b75513..432bbcd 100644
--- a/samples/ApiDemos/res/xml/device_admin_sample.xml
+++ b/samples/ApiDemos/res/xml/device_admin_sample.xml
@@ -4,9 +4,9 @@
      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.
@@ -23,6 +23,7 @@
         <force-lock />
         <wipe-data />
         <set-global-proxy />
+        <expire-password />
     </uses-policies>
 </device-admin>
 <!-- END_INCLUDE(meta_data) -->
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/DeviceAdminSample.java b/samples/ApiDemos/src/com/example/android/apis/app/DeviceAdminSample.java
index 59baefc..94c0ead 100644
--- a/samples/ApiDemos/src/com/example/android/apis/app/DeviceAdminSample.java
+++ b/samples/ApiDemos/src/com/example/android/apis/app/DeviceAdminSample.java
@@ -18,6 +18,7 @@
 
 import com.example.android.apis.R;
 
+import android.R.menu;
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.AlertDialog;
@@ -44,7 +45,9 @@
 
 import java.net.InetSocketAddress;
 import java.net.Proxy;
+import java.text.DateFormat;
 import java.util.Arrays;
+import java.util.Date;
 import java.util.List;
 
 /**
@@ -53,6 +56,11 @@
  */
 public class DeviceAdminSample extends DeviceAdminReceiver {
 
+    private static final String TAG = "DeviceAdminSample";
+    private static final long MS_PER_DAY = 86400 * 1000;
+    private static final long MS_PER_HOUR = 3600 * 1000;
+    private static final long MS_PER_MINUTE = 60 * 1000;
+
     static SharedPreferences getSamplePreferences(Context context) {
         return context.getSharedPreferences(DeviceAdminReceiver.class.getName(), 0);
     }
@@ -66,15 +74,16 @@
     static String PREF_PASSWORD_MINIMUM_SYMBOLS = "password_minimum_symbols";
     static String PREF_PASSWORD_MINIMUM_NONLETTER = "password_minimum_nonletter";
     static String PREF_PASSWORD_HISTORY_LENGTH = "password_history_length";
+    static String PREF_PASSWORD_EXPIRATION_TIMEOUT = "password_expiration_timeout";
     static String PREF_MAX_FAILED_PW = "max_failed_pw";
 
     void showToast(Context context, CharSequence msg) {
-        Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
+        Toast.makeText(context, "Sample Device Admin: " + msg, Toast.LENGTH_SHORT).show();
     }
 
     @Override
     public void onEnabled(Context context, Intent intent) {
-        showToast(context, "Sample Device Admin: enabled");
+        showToast(context, "enabled");
     }
 
     @Override
@@ -84,22 +93,43 @@
 
     @Override
     public void onDisabled(Context context, Intent intent) {
-        showToast(context, "Sample Device Admin: disabled");
+        showToast(context, "disabled");
     }
 
     @Override
     public void onPasswordChanged(Context context, Intent intent) {
-        showToast(context, "Sample Device Admin: pw changed");
+        showToast(context, "pw changed");
     }
 
     @Override
     public void onPasswordFailed(Context context, Intent intent) {
-        showToast(context, "Sample Device Admin: pw failed");
+        showToast(context, "pw failed");
     }
 
     @Override
     public void onPasswordSucceeded(Context context, Intent intent) {
-        showToast(context, "Sample Device Admin: pw succeeded");
+        showToast(context, "pw succeeded");
+    }
+
+    static String countdownString(long time) {
+        long days = time / MS_PER_DAY;
+        long hours = (time / MS_PER_HOUR) % 24;
+        long minutes = (time / MS_PER_MINUTE) % 60;
+        return days + "d" + hours + "h" + minutes + "m";
+    }
+
+    @Override
+    public void onPasswordExpiring(Context context, Intent intent) {
+        DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
+                Context.DEVICE_POLICY_SERVICE);
+        long expr = dpm.getPasswordExpiration(new ComponentName(context, DeviceAdminSample.class));
+        long delta = expr - System.currentTimeMillis();
+        boolean expired = delta < 0L;
+        String msg = expired ? "Password expired " : "Password will expire "
+                + countdownString(Math.abs(delta))
+                + (expired ? " ago" : " from now");
+        showToast(context, msg);
+        Log.v(TAG, msg);
     }
 
     /**
@@ -112,6 +142,7 @@
      */
     public static class Controller extends Activity {
         static final int RESULT_ENABLE = 1;
+        private static final long MS_PER_MINUTE = 60*1000;
 
         DevicePolicyManager mDPM;
         ActivityManager mAM;
@@ -130,6 +161,7 @@
             DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC,
             DevicePolicyManager.PASSWORD_QUALITY_COMPLEX
         };
+
         Spinner mPasswordQuality;
         EditText mPasswordLength;
         EditText mPasswordMinimumLetters;
@@ -139,6 +171,7 @@
         EditText mPasswordMinimumSymbols;
         EditText mPasswordMinimumNonLetter;
         EditText mPasswordHistoryLength;
+        EditText mPasswordExpirationTimeout;
         Button mSetPasswordButton;
 
         EditText mPassword;
@@ -158,6 +191,8 @@
         EditText mProxyList;
         Button mProxyButton;
 
+        private Button mPasswordExpirationButton;
+
         @Override
         protected void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
@@ -294,6 +329,16 @@
                     }
                 }
             });
+
+            mPasswordExpirationTimeout = (EditText)findViewById(R.id.password_expiration);
+            mPasswordExpirationButton = (Button) findViewById(R.id.update_expiration_button);
+            mPasswordExpirationButton.setOnClickListener(new OnClickListener() {
+                public void onClick(View v) {
+                    setPasswordExpiration(
+                            Long.parseLong(mPasswordExpirationTimeout.getText().toString()));
+                }
+            });
+
             mSetPasswordButton = (Button)findViewById(R.id.set_password);
             mSetPasswordButton.setOnClickListener(mSetPasswordListener);
 
@@ -390,6 +435,7 @@
             final int pwMinSymbols = prefs.getInt(PREF_PASSWORD_MINIMUM_SYMBOLS, 0);
             final int pwMinNonLetter = prefs.getInt(PREF_PASSWORD_MINIMUM_NONLETTER, 0);
             final int pwHistoryLength = prefs.getInt(PREF_PASSWORD_HISTORY_LENGTH, 0);
+            final long pwExpirationTimeout = prefs.getLong(PREF_PASSWORD_EXPIRATION_TIMEOUT, 0L);
             final int maxFailedPw = prefs.getInt(PREF_MAX_FAILED_PW, 0);
 
             for (int i=0; i<mPasswordQualityValues.length; i++) {
@@ -405,6 +451,7 @@
             mPasswordMinimumNumeric.setText(Integer.toString(pwMinNumeric));
             mPasswordMinimumNonLetter.setText(Integer.toString(pwMinNonLetter));
             mPasswordHistoryLength.setText(Integer.toString(pwHistoryLength));
+            mPasswordExpirationTimeout.setText(Long.toString(pwExpirationTimeout/MS_PER_MINUTE));
             mMaxFailedPw.setText(Integer.toString(maxFailedPw));
         }
 
@@ -420,6 +467,7 @@
             final int pwMinSymbols = prefs.getInt(PREF_PASSWORD_MINIMUM_SYMBOLS, 0);
             final int pwMinNonLetter = prefs.getInt(PREF_PASSWORD_MINIMUM_NONLETTER, 0);
             final int pwHistoryLength = prefs.getInt(PREF_PASSWORD_HISTORY_LENGTH, 0);
+            final long pwExpiration = prefs.getLong(PREF_PASSWORD_EXPIRATION_TIMEOUT, 0L);
             final int maxFailedPw = prefs.getInt(PREF_MAX_FAILED_PW, 0);
 
             boolean active = mDPM.isAdminActive(mDeviceAdminSample);
@@ -434,6 +482,7 @@
                 mDPM.setPasswordMinimumNonLetter(mDeviceAdminSample, pwMinNonLetter);
                 mDPM.setPasswordHistoryLength(mDeviceAdminSample, pwHistoryLength);
                 mDPM.setMaximumFailedPasswordsForWipe(mDeviceAdminSample, maxFailedPw);
+                mDPM.setPasswordExpirationTimeout(mDeviceAdminSample, pwExpiration);
             }
         }
 
@@ -491,6 +540,22 @@
             updatePolicies();
         }
 
+        void setPasswordExpiration(long expiration) {
+            SharedPreferences prefs = getSamplePreferences(this);
+            long exp = expiration * MS_PER_MINUTE; // convert from UI units to ms
+            prefs.edit().putLong(PREF_PASSWORD_EXPIRATION_TIMEOUT, exp).commit();
+            updatePolicies();
+            // Show confirmation dialog
+            long confirm = mDPM.getPasswordExpiration(mDeviceAdminSample);
+            String date = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT)
+                    .format(new Date(confirm));
+            new AlertDialog.Builder(this)
+                    .setMessage("Password will expire on " + date)
+                    .setPositiveButton("OK", null)
+                    .create()
+                    .show();
+        }
+
         void setMaxFailedPw(int length) {
             SharedPreferences prefs = getSamplePreferences(this);
             prefs.edit().putInt(PREF_MAX_FAILED_PW, length).commit();
@@ -508,9 +573,9 @@
             switch (requestCode) {
                 case RESULT_ENABLE:
                     if (resultCode == Activity.RESULT_OK) {
-                        Log.i("DeviceAdminSample", "Admin enabled!");
+                        Log.i(TAG, "Admin enabled!");
                     } else {
-                        Log.i("DeviceAdminSample", "Admin enable FAILED!");
+                        Log.i(TAG, "Admin enable FAILED!");
                     }
                     return;
             }