SecurityPolicy.java revision a2269e84c6134bfd3506e5489c7ccfd60c32d41f
1345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler/*
2345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler * Copyright (C) 2010 The Android Open Source Project
3345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler *
4345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler * Licensed under the Apache License, Version 2.0 (the "License");
5345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler * you may not use this file except in compliance with the License.
6345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler * You may obtain a copy of the License at
7345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler *
8345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler *      http://www.apache.org/licenses/LICENSE-2.0
9345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler *
10345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler * Unless required by applicable law or agreed to in writing, software
11345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler * distributed under the License is distributed on an "AS IS" BASIS,
12345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler * See the License for the specific language governing permissions and
14345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler * limitations under the License.
15345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler */
16345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler
17345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadlerpackage com.android.email;
18345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler
193d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadlerimport com.android.email.activity.setup.AccountSecurity;
203d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadlerimport com.android.email.provider.EmailContent;
21345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadlerimport com.android.email.provider.EmailContent.Account;
223d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadlerimport com.android.email.provider.EmailContent.AccountColumns;
23a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadlerimport com.android.email.service.EmailBroadcastProcessorService;
24345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler
25e7f4d3ebfcf497c015ba65be7ecebea8926b995cAndy Stadlerimport android.app.admin.DeviceAdminInfo;
266d0016229adc13fefe68820fe4d6e46f530952baDianne Hackbornimport android.app.admin.DeviceAdminReceiver;
276d0016229adc13fefe68820fe4d6e46f530952baDianne Hackbornimport android.app.admin.DevicePolicyManager;
28d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadlerimport android.content.ComponentName;
2902d59d21949a77c60859b615312f02e6d8003490Marc Blankimport android.content.ContentResolver;
303d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadlerimport android.content.ContentValues;
31345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadlerimport android.content.Context;
32345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadlerimport android.content.Intent;
33345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadlerimport android.database.Cursor;
34d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blankimport android.os.Parcel;
35d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blankimport android.os.Parcelable;
3650d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadlerimport android.util.Log;
37345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler
38345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler/**
39d71d0b223a5cd02e2a8f1ec5c3f8cebab170d65fAndrew Stadler * Utility functions to support reading and writing security policies, and handshaking the device
40d71d0b223a5cd02e2a8f1ec5c3f8cebab170d65fAndrew Stadler * into and out of various security states.
41345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler */
42345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadlerpublic class SecurityPolicy {
4302d59d21949a77c60859b615312f02e6d8003490Marc Blank    private static final String TAG = "SecurityPolicy";
44345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    private static SecurityPolicy sInstance = null;
45345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    private Context mContext;
46d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler    private DevicePolicyManager mDPM;
47d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler    private ComponentName mAdminName;
48d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler    private PolicySet mAggregatePolicy;
49d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler
50af55e3e436991fde91cdc80efe2786eb8f509d15Andrew Stadler    /* package */ static final PolicySet NO_POLICY_SET =
519b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank            new PolicySet(0, PolicySet.PASSWORD_MODE_NONE, 0, 0, false, 0, 0, 0);
52345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler
53345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    /**
54968be441b4c253668c4ee1c7a3f8e4b0eb12cf24Makoto Onuki     * This projection on Account is for scanning/reading
55345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     */
56345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    private static final String[] ACCOUNT_SECURITY_PROJECTION = new String[] {
572a5eeea9213005060256054ec773e72406415ce4Andrew Stadler        AccountColumns.ID, AccountColumns.SECURITY_FLAGS
58345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    };
591ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler    private static final int ACCOUNT_SECURITY_COLUMN_ID = 0;
60345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    private static final int ACCOUNT_SECURITY_COLUMN_FLAGS = 1;
612a5eeea9213005060256054ec773e72406415ce4Andrew Stadler
62a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler    // Messages used for DevicePolicyManager callbacks
63a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler    private static final int DEVICE_ADMIN_MESSAGE_ENABLED = 1;
64a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler    private static final int DEVICE_ADMIN_MESSAGE_DISABLED = 2;
65a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler    private static final int DEVICE_ADMIN_MESSAGE_PASSWORD_CHANGED = 3;
66a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler    private static final int DEVICE_ADMIN_MESSAGE_PASSWORD_EXPIRING = 4;
67a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler
68345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    /**
69345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     * Get the security policy instance
70345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     */
71345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    public synchronized static SecurityPolicy getInstance(Context context) {
72345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        if (sInstance == null) {
73a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler            sInstance = new SecurityPolicy(context.getApplicationContext());
74345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        }
75345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        return sInstance;
76345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    }
77345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler
78345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    /**
79345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     * Private constructor (one time only)
80345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     */
81345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    private SecurityPolicy(Context context) {
82968be441b4c253668c4ee1c7a3f8e4b0eb12cf24Makoto Onuki        mContext = context.getApplicationContext();
83d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler        mDPM = null;
84d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler        mAdminName = new ComponentName(context, PolicyAdmin.class);
85d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler        mAggregatePolicy = null;
86345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    }
87345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler
88345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    /**
89345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     * For testing only: Inject context into already-created instance
90345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     */
91345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    /* package */ void setContext(Context context) {
92345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        mContext = context;
93345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    }
94345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler
95345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    /**
96345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     * Compute the aggregate policy for all accounts that require it, and record it.
97345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     *
98345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     * The business logic is as follows:
99345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     *  min password length         take the max
100345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     *  password mode               take the max (strongest mode)
101345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     *  max password fails          take the min
102345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     *  max screen lock time        take the min
103345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     *  require remote wipe         take the max (logical or)
1049b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank     *  password history            take the max (strongest mode)
1051ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     *  password expiration         take the min (strongest mode)
1069b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank     *  password complex chars      take the max (strongest mode)
107968be441b4c253668c4ee1c7a3f8e4b0eb12cf24Makoto Onuki     *
108d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler     * @return a policy representing the strongest aggregate.  If no policy sets are defined,
109d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler     * a lightweight "nothing required" policy will be returned.  Never null.
110345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     */
1119b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank    /*package*/ PolicySet computeAggregatePolicy() {
112345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        boolean policiesFound = false;
113345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler
114345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        int minPasswordLength = Integer.MIN_VALUE;
115345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        int passwordMode = Integer.MIN_VALUE;
116345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        int maxPasswordFails = Integer.MAX_VALUE;
117345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        int maxScreenLockTime = Integer.MAX_VALUE;
118345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        boolean requireRemoteWipe = false;
1199b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank        int passwordHistory = Integer.MIN_VALUE;
1201ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        int passwordExpirationDays = Integer.MAX_VALUE;
1219b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank        int passwordComplexChars = Integer.MIN_VALUE;
122345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler
123345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        Cursor c = mContext.getContentResolver().query(Account.CONTENT_URI,
124bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki                ACCOUNT_SECURITY_PROJECTION, Account.SECURITY_NONZERO_SELECTION, null, null);
125345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        try {
126345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler            while (c.moveToNext()) {
1279b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank                long flags = c.getLong(ACCOUNT_SECURITY_COLUMN_FLAGS);
128345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler                if (flags != 0) {
129345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler                    PolicySet p = new PolicySet(flags);
1303d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler                    minPasswordLength = Math.max(p.mMinPasswordLength, minPasswordLength);
1313d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler                    passwordMode  = Math.max(p.mPasswordMode, passwordMode);
132345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler                    if (p.mMaxPasswordFails > 0) {
133345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler                        maxPasswordFails = Math.min(p.mMaxPasswordFails, maxPasswordFails);
134345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler                    }
135345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler                    if (p.mMaxScreenLockTime > 0) {
136345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler                        maxScreenLockTime = Math.min(p.mMaxScreenLockTime, maxScreenLockTime);
137345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler                    }
1389b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank                    if (p.mPasswordHistory > 0) {
1399b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank                        passwordHistory = Math.max(p.mPasswordHistory, passwordHistory);
1409b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank                    }
1411ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                    if (p.mPasswordExpirationDays > 0) {
1421ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                        passwordExpirationDays =
1431ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                                Math.min(p.mPasswordExpirationDays, passwordExpirationDays);
1449b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank                    }
1459b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank                    if (p.mPasswordComplexChars > 0) {
1469b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank                        passwordComplexChars = Math.max(p.mPasswordComplexChars,
1479b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank                                passwordComplexChars);
1489b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank                    }
149345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler                    requireRemoteWipe |= p.mRequireRemoteWipe;
150345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler                    policiesFound = true;
151345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler                }
152345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler            }
153345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        } finally {
154345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler            c.close();
155345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        }
156345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        if (policiesFound) {
1573d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler            // final cleanup pass converts any untouched min/max values to zero (not specified)
1583d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler            if (minPasswordLength == Integer.MIN_VALUE) minPasswordLength = 0;
1593d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler            if (passwordMode == Integer.MIN_VALUE) passwordMode = 0;
1603d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler            if (maxPasswordFails == Integer.MAX_VALUE) maxPasswordFails = 0;
1613d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler            if (maxScreenLockTime == Integer.MAX_VALUE) maxScreenLockTime = 0;
1629b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank            if (passwordHistory == Integer.MIN_VALUE) passwordHistory = 0;
1631ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            if (passwordExpirationDays == Integer.MAX_VALUE) passwordExpirationDays = 0;
1649b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank            if (passwordComplexChars == Integer.MIN_VALUE) passwordComplexChars = 0;
1653d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler
166345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler            return new PolicySet(minPasswordLength, passwordMode, maxPasswordFails,
1671ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                    maxScreenLockTime, requireRemoteWipe, passwordExpirationDays, passwordHistory,
1689b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank                    passwordComplexChars);
169345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        } else {
170d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler            return NO_POLICY_SET;
171345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        }
172345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    }
173345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler
174345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    /**
17550d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler     * Return updated aggregate policy, from cached value if possible
17650d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler     */
17750d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler    public synchronized PolicySet getAggregatePolicy() {
17850d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler        if (mAggregatePolicy == null) {
17950d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler            mAggregatePolicy = computeAggregatePolicy();
18050d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler        }
18150d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler        return mAggregatePolicy;
18250d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler    }
18350d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler
18450d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler    /**
185d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler     * Get the dpm.  This mainly allows us to make some utility calls without it, for testing.
186d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler     */
187d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler    private synchronized DevicePolicyManager getDPM() {
188d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler        if (mDPM == null) {
189d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler            mDPM = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
190d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler        }
191d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler        return mDPM;
192d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler    }
193d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler
194d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler    /**
195d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler     * API: Report that policies may have been updated due to rewriting values in an Account.
19650d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler     * @param accountId the account that has been updated, -1 if unknown/deleted
197d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler     */
1983d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler    public synchronized void updatePolicies(long accountId) {
199d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler        mAggregatePolicy = null;
200d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler    }
201d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler
202d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler    /**
20350d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler     * API: Report that policies may have been updated *and* the caller vouches that the
20450d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler     * change is a reduction in policies.  This forces an immediate change to device state.
20550d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler     * Typically used when deleting accounts, although we may use it for server-side policy
20650d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler     * rollbacks.
20750d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler     */
20850d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler    public void reducePolicies() {
20950d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler        updatePolicies(-1);
21050d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler        setActivePolicies();
21150d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler    }
21250d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler
21350d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler    /**
214d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler     * API: Query used to determine if a given policy is "active" (the device is operating at
2153d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     * the required security level).
216345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     *
2173d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     * This can be used when syncing a specific account, by passing a specific set of policies
2183d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     * for that account.  Or, it can be used at any time to compare the device
2193d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     * state against the aggregate set of device policies stored in all accounts.
2203d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     *
2213d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     * This method is for queries only, and does not trigger any change in device state.
2223d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     *
2231ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * NOTE:  If there are multiple accounts with password expiration policies, the device
2241ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * password will be set to expire in the shortest required interval (most secure).  This method
2251ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * will return 'false' as soon as the password expires - irrespective of which account caused
2261ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * the expiration.  In other words, all accounts (that require expiration) will run/stop
2271ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * based on the requirements of the account with the shortest interval.
2281ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     *
2293d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     * @param policies the policies requested, or null to check aggregate stored policies
230345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     * @return true if the policies are active, false if not active
231345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     */
232345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    public boolean isActive(PolicySet policies) {
23350d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler        // select aggregate set if needed
23450d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler        if (policies == null) {
23550d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler            policies = getAggregatePolicy();
23650d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler        }
23750d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler        // quick check for the "empty set" of no policies
23850d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler        if (policies == NO_POLICY_SET) {
23950d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler            return true;
24050d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler        }
241d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler        DevicePolicyManager dpm = getDPM();
242e7f4d3ebfcf497c015ba65be7ecebea8926b995cAndy Stadler        if (isActiveAdmin()) {
243d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler            // check each policy explicitly
2443d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler            if (policies.mMinPasswordLength > 0) {
2453d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler                if (dpm.getPasswordMinimumLength(mAdminName) < policies.mMinPasswordLength) {
246d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler                    return false;
247d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler                }
248d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler            }
2493d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler            if (policies.mPasswordMode > 0) {
2503d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler                if (dpm.getPasswordQuality(mAdminName) < policies.getDPManagerPasswordQuality()) {
251d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler                    return false;
252d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler                }
253d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler                if (!dpm.isActivePasswordSufficient()) {
254d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler                    return false;
255d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler                }
256d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler            }
2573d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler            if (policies.mMaxScreenLockTime > 0) {
258d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler                // Note, we use seconds, dpm uses milliseconds
2593d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler                if (dpm.getMaximumTimeToLock(mAdminName) > policies.mMaxScreenLockTime * 1000) {
260d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler                    return false;
261d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler                }
262d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler            }
2631ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            if (policies.mPasswordExpirationDays > 0) {
2641ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                // confirm that expirations are currently set
2651ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                long currentTimeout = dpm.getPasswordExpirationTimeout(mAdminName);
2661ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                if (currentTimeout == 0
2671ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                        || currentTimeout > policies.getDPManagerPasswordExpirationTimeout()) {
2681ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                    return false;
2691ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                }
2701ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                // confirm that the current password hasn't expired
2711ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                long expirationDate = dpm.getPasswordExpiration(mAdminName);
2721ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                long timeUntilExpiration = expirationDate - System.currentTimeMillis();
2731ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                boolean expired = timeUntilExpiration < 0;
2741ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                if (expired) {
2751ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                    return false;
2761ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                }
2779b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank            }
2789b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank            if (policies.mPasswordHistory > 0) {
2799b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank                if (dpm.getPasswordHistoryLength(mAdminName) < policies.mPasswordHistory) {
2809b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank                    return false;
2819b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank                }
2829b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank            }
2839b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank            if (policies.mPasswordComplexChars > 0) {
2849b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank                if (dpm.getPasswordMinimumNonLetter(mAdminName) < policies.mPasswordComplexChars) {
2859b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank                    return false;
2869b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank                }
2879b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank            }
288d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler            // password failures are counted locally - no test required here
289d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler            // no check required for remote wipe (it's supported, if we're the admin)
2902a5eeea9213005060256054ec773e72406415ce4Andrew Stadler
2912a5eeea9213005060256054ec773e72406415ce4Andrew Stadler            // making it this far means we passed!
2922a5eeea9213005060256054ec773e72406415ce4Andrew Stadler            return true;
293d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler        }
294d71d0b223a5cd02e2a8f1ec5c3f8cebab170d65fAndrew Stadler        // return false, not active
295d71d0b223a5cd02e2a8f1ec5c3f8cebab170d65fAndrew Stadler        return false;
296345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    }
297345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler
298345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    /**
29950d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler     * Set the requested security level based on the aggregate set of requests.
30050d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler     * If the set is empty, we release our device administration.  If the set is non-empty,
30150d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler     * we only proceed if we are already active as an admin.
3023d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     */
3033d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler    public void setActivePolicies() {
3043d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler        DevicePolicyManager dpm = getDPM();
30550d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler        // compute aggregate set of policies
30650d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler        PolicySet policies = getAggregatePolicy();
30750d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler        // if empty set, detach from policy manager
30850d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler        if (policies == NO_POLICY_SET) {
30950d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler            dpm.removeActiveAdmin(mAdminName);
310e7f4d3ebfcf497c015ba65be7ecebea8926b995cAndy Stadler        } else if (isActiveAdmin()) {
3113d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler            // set each policy in the policy manager
3123d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler            // password mode & length
3133d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler            dpm.setPasswordQuality(mAdminName, policies.getDPManagerPasswordQuality());
3143d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler            dpm.setPasswordMinimumLength(mAdminName, policies.mMinPasswordLength);
3153d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler            // screen lock time
3163d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler            dpm.setMaximumTimeToLock(mAdminName, policies.mMaxScreenLockTime * 1000);
3173d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler            // local wipe (failed passwords limit)
3183d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler            dpm.setMaximumFailedPasswordsForWipe(mAdminName, policies.mMaxPasswordFails);
3191ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            // password expiration (days until a password expires).  API takes mSec.
3201ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            dpm.setPasswordExpirationTimeout(mAdminName,
3211ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                    policies.getDPManagerPasswordExpirationTimeout());
3229b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank            // password history length (number of previous passwords that may not be reused)
3239b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank            dpm.setPasswordHistoryLength(mAdminName, policies.mPasswordHistory);
3249b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank            // password minimum complex characters
3259b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank            dpm.setPasswordMinimumNonLetter(mAdminName, policies.mPasswordComplexChars);
3263d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler        }
3273d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler    }
3283d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler
3293d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler    /**
3302a5eeea9213005060256054ec773e72406415ce4Andrew Stadler     * API: Set/Clear the "hold" flag in any account.  This flag serves a dual purpose:
3312a5eeea9213005060256054ec773e72406415ce4Andrew Stadler     * Setting it gives us an indication that it was blocked, and clearing it gives EAS a
3322a5eeea9213005060256054ec773e72406415ce4Andrew Stadler     * signal to try syncing again.
3331ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * @param context
3341ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * @param account The account to update
3351ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * @param newState true = security hold, false = free to sync
3362a5eeea9213005060256054ec773e72406415ce4Andrew Stadler     */
3371ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler    public static void setAccountHoldFlag(Context context, Account account, boolean newState) {
3382a5eeea9213005060256054ec773e72406415ce4Andrew Stadler        if (newState) {
3392a5eeea9213005060256054ec773e72406415ce4Andrew Stadler            account.mFlags |= Account.FLAGS_SECURITY_HOLD;
3402a5eeea9213005060256054ec773e72406415ce4Andrew Stadler        } else {
3412a5eeea9213005060256054ec773e72406415ce4Andrew Stadler            account.mFlags &= ~Account.FLAGS_SECURITY_HOLD;
3422a5eeea9213005060256054ec773e72406415ce4Andrew Stadler        }
3432a5eeea9213005060256054ec773e72406415ce4Andrew Stadler        ContentValues cv = new ContentValues();
3442a5eeea9213005060256054ec773e72406415ce4Andrew Stadler        cv.put(AccountColumns.FLAGS, account.mFlags);
3451ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        account.update(context, cv);
3462a5eeea9213005060256054ec773e72406415ce4Andrew Stadler    }
3472a5eeea9213005060256054ec773e72406415ce4Andrew Stadler
3482a5eeea9213005060256054ec773e72406415ce4Andrew Stadler    /**
3493d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     * API: Sync service should call this any time a sync fails due to isActive() returning false.
350d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler     * This will kick off the notify-acquire-admin-state process and/or increase the security level.
351345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     * The caller needs to write the required policies into this account before making this call.
3523d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     * Should not be called from UI thread - uses DB lookups to prepare new notifications
353345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     *
354345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     * @param accountId the account for which sync cannot proceed
355345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     */
356345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    public void policiesRequired(long accountId) {
3572a5eeea9213005060256054ec773e72406415ce4Andrew Stadler        Account account = EmailContent.Account.restoreAccountWithId(mContext, accountId);
3581ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler
3592a5eeea9213005060256054ec773e72406415ce4Andrew Stadler        // Mark the account as "on hold".
3601ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        setAccountHoldFlag(mContext, account, true);
3611ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler
3621ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        // Put up a notification
3633d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler        String tickerText = mContext.getString(R.string.security_notification_ticker_fmt,
3643d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler                account.getDisplayName());
3653d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler        String contentTitle = mContext.getString(R.string.security_notification_content_title);
3663d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler        String contentText = account.getDisplayName();
3673d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler        Intent intent = AccountSecurity.actionUpdateSecurityIntent(mContext, accountId);
3681ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        NotificationController.getInstance(mContext).postAccountNotification(
3691ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                account, tickerText, contentTitle, contentText, intent,
3701ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                NotificationController.NOTIFICATION_ID_SECURITY_NEEDED);
3713d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler    }
3723d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler
3733d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler    /**
3743d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     * Called from the notification's intent receiver to register that the notification can be
3753d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     * cleared now.
3763d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     */
377a87f8d8bbc0b704974c4230f518cf23b7215fc55Andrew Stadler    public void clearNotification(long accountId) {
3781ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        NotificationController.getInstance(mContext).cancelNotification(
3791ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                NotificationController.NOTIFICATION_ID_SECURITY_NEEDED);
3803d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler    }
3813d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler
3823d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler    /**
38350d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler     * API: Remote wipe (from server).  This is final, there is no confirmation.  It will only
38450d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler     * return to the caller if there is an unexpected failure.
3853d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     */
38650d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler    public void remoteWipe() {
3873d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler        DevicePolicyManager dpm = getDPM();
3883d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler        if (dpm.isAdminActive(mAdminName)) {
3893d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler            dpm.wipeData(0);
39050d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler        } else {
39150d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler            Log.d(Email.LOG_TAG, "Could not remote wipe because not device admin.");
3923d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler        }
393345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    }
394345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler
395345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    /**
396345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     * Class for tracking policies and reading/writing into accounts
397345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     */
398d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank    public static class PolicySet implements Parcelable {
399345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler
400345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        // Security (provisioning) flags
401345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler            // bits 0..4: password length (0=no password required)
402345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        private static final int PASSWORD_LENGTH_MASK = 31;
403345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        private static final int PASSWORD_LENGTH_SHIFT = 0;
404a843d40ba1d3eb77e76b4a28aa911588f0fd81a1Andrew Stadler        public static final int PASSWORD_LENGTH_MAX = 30;
405345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler            // bits 5..8: password mode
406345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        private static final int PASSWORD_MODE_SHIFT = 5;
407345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        private static final int PASSWORD_MODE_MASK = 15 << PASSWORD_MODE_SHIFT;
408345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        public static final int PASSWORD_MODE_NONE = 0 << PASSWORD_MODE_SHIFT;
409345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        public static final int PASSWORD_MODE_SIMPLE = 1 << PASSWORD_MODE_SHIFT;
410345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        public static final int PASSWORD_MODE_STRONG = 2 << PASSWORD_MODE_SHIFT;
411345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler            // bits 9..13: password failures -> wipe device (0=disabled)
412345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        private static final int PASSWORD_MAX_FAILS_SHIFT = 9;
413345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        private static final int PASSWORD_MAX_FAILS_MASK = 31 << PASSWORD_MAX_FAILS_SHIFT;
414345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        public static final int PASSWORD_MAX_FAILS_MAX = 31;
415345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler            // bits 14..24: seconds to screen lock (0=not required)
416345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        private static final int SCREEN_LOCK_TIME_SHIFT = 14;
417345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        private static final int SCREEN_LOCK_TIME_MASK = 2047 << SCREEN_LOCK_TIME_SHIFT;
418345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        public static final int SCREEN_LOCK_TIME_MAX = 2047;
419345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler            // bit 25: remote wipe capability required
420345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        private static final int REQUIRE_REMOTE_WIPE = 1 << 25;
4219b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank            // bit 26..35: password expiration (days; 0=not required)
4229b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank        private static final int PASSWORD_EXPIRATION_SHIFT = 26;
4239b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank        private static final long PASSWORD_EXPIRATION_MASK = 1023L << PASSWORD_EXPIRATION_SHIFT;
4249b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank        public static final int PASSWORD_EXPIRATION_MAX = 1023;
4259b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank            // bit 35..42: password history (length; 0=not required)
4269b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank        private static final int PASSWORD_HISTORY_SHIFT = 36;
4279b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank        private static final long PASSWORD_HISTORY_MASK = 255L << PASSWORD_HISTORY_SHIFT;
4289b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank        public static final int PASSWORD_HISTORY_MAX = 255;
4299b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank            // bit 42..46: min complex characters (0=not required)
4309b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank        private static final int PASSWORD_COMPLEX_CHARS_SHIFT = 44;
4319b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank        private static final long PASSWORD_COMPLEX_CHARS_MASK = 31L << PASSWORD_COMPLEX_CHARS_SHIFT;
4329b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank        public static final int PASSWORD_COMPLEX_CHARS_MAX = 31;
433345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler
4341ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        /* Convert days to mSec (used for password expiration) */
4351ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        private static final long DAYS_TO_MSEC = 24 * 60 * 60 * 1000;
4361ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        /* Small offset (2 minutes) added to policy expiration to make user testing easier. */
4371ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        private static final long EXPIRATION_OFFSET_MSEC = 2 * 60 * 1000;
4381ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler
4391d6dab29562eca7978f179be5f5c75f22f44d734Marc Blank        /*package*/ final int mMinPasswordLength;
4401d6dab29562eca7978f179be5f5c75f22f44d734Marc Blank        /*package*/ final int mPasswordMode;
4411d6dab29562eca7978f179be5f5c75f22f44d734Marc Blank        /*package*/ final int mMaxPasswordFails;
4421d6dab29562eca7978f179be5f5c75f22f44d734Marc Blank        /*package*/ final int mMaxScreenLockTime;
4431d6dab29562eca7978f179be5f5c75f22f44d734Marc Blank        /*package*/ final boolean mRequireRemoteWipe;
4441ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        /*package*/ final int mPasswordExpirationDays;
4459b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank        /*package*/ final int mPasswordHistory;
4469b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank        /*package*/ final int mPasswordComplexChars;
4471d6dab29562eca7978f179be5f5c75f22f44d734Marc Blank
4486278dcdeafadc55fe1a57eec42a0807874377f62Andy Stadler        public int getMinPasswordLengthForTest() {
4491d6dab29562eca7978f179be5f5c75f22f44d734Marc Blank            return mMinPasswordLength;
4501d6dab29562eca7978f179be5f5c75f22f44d734Marc Blank        }
4511d6dab29562eca7978f179be5f5c75f22f44d734Marc Blank
4526278dcdeafadc55fe1a57eec42a0807874377f62Andy Stadler        public int getPasswordModeForTest() {
4531d6dab29562eca7978f179be5f5c75f22f44d734Marc Blank            return mPasswordMode;
4541d6dab29562eca7978f179be5f5c75f22f44d734Marc Blank        }
4551d6dab29562eca7978f179be5f5c75f22f44d734Marc Blank
4566278dcdeafadc55fe1a57eec42a0807874377f62Andy Stadler        public int getMaxPasswordFailsForTest() {
4571d6dab29562eca7978f179be5f5c75f22f44d734Marc Blank            return mMaxPasswordFails;
4581d6dab29562eca7978f179be5f5c75f22f44d734Marc Blank        }
4591d6dab29562eca7978f179be5f5c75f22f44d734Marc Blank
4606278dcdeafadc55fe1a57eec42a0807874377f62Andy Stadler        public int getMaxScreenLockTimeForTest() {
4611d6dab29562eca7978f179be5f5c75f22f44d734Marc Blank            return mMaxScreenLockTime;
4621d6dab29562eca7978f179be5f5c75f22f44d734Marc Blank        }
4631d6dab29562eca7978f179be5f5c75f22f44d734Marc Blank
4646278dcdeafadc55fe1a57eec42a0807874377f62Andy Stadler        public boolean isRequireRemoteWipeForTest() {
4651d6dab29562eca7978f179be5f5c75f22f44d734Marc Blank            return mRequireRemoteWipe;
4661d6dab29562eca7978f179be5f5c75f22f44d734Marc Blank        }
467345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler
468345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        /**
469345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler         * Create from raw values.
470345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler         * @param minPasswordLength (0=not enforced)
471345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler         * @param passwordMode
472345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler         * @param maxPasswordFails (0=not enforced)
473d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler         * @param maxScreenLockTime in seconds (0=not enforced)
474345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler         * @param requireRemoteWipe
4751ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler         * @param passwordExpirationDays in days (0=not enforced)
4761ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler         * @param passwordHistory (0=not enforced)
4771ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler         * @param passwordComplexChars (0=not enforced)
478a843d40ba1d3eb77e76b4a28aa911588f0fd81a1Andrew Stadler         * @throws IllegalArgumentException for illegal arguments.
479345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler         */
480345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        public PolicySet(int minPasswordLength, int passwordMode, int maxPasswordFails,
4811ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                int maxScreenLockTime, boolean requireRemoteWipe, int passwordExpirationDays,
4829b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank                int passwordHistory, int passwordComplexChars) throws IllegalArgumentException {
483c263810b08943541135a24e2b7520692152455ccMarc Blank            // If we're not enforcing passwords, make sure we clean up related values, since EAS
484c263810b08943541135a24e2b7520692152455ccMarc Blank            // can send non-zero values for any or all of these
485c263810b08943541135a24e2b7520692152455ccMarc Blank            if (passwordMode == PASSWORD_MODE_NONE) {
486c263810b08943541135a24e2b7520692152455ccMarc Blank                maxPasswordFails = 0;
487c263810b08943541135a24e2b7520692152455ccMarc Blank                maxScreenLockTime = 0;
488c263810b08943541135a24e2b7520692152455ccMarc Blank                minPasswordLength = 0;
489c263810b08943541135a24e2b7520692152455ccMarc Blank                passwordComplexChars = 0;
490c263810b08943541135a24e2b7520692152455ccMarc Blank                passwordHistory = 0;
4911ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                passwordExpirationDays = 0;
492c263810b08943541135a24e2b7520692152455ccMarc Blank            } else {
493c263810b08943541135a24e2b7520692152455ccMarc Blank                if ((passwordMode != PASSWORD_MODE_SIMPLE) &&
494c263810b08943541135a24e2b7520692152455ccMarc Blank                        (passwordMode != PASSWORD_MODE_STRONG)) {
495c263810b08943541135a24e2b7520692152455ccMarc Blank                    throw new IllegalArgumentException("password mode");
496c263810b08943541135a24e2b7520692152455ccMarc Blank                }
49761911d4ff70132fa21c5ee7a987303479e8ef6aeMarc Blank                // If we're only requiring a simple password, set complex chars to zero; note
49861911d4ff70132fa21c5ee7a987303479e8ef6aeMarc Blank                // that EAS can erroneously send non-zero values in this case
49961911d4ff70132fa21c5ee7a987303479e8ef6aeMarc Blank                if (passwordMode == PASSWORD_MODE_SIMPLE) {
50061911d4ff70132fa21c5ee7a987303479e8ef6aeMarc Blank                    passwordComplexChars = 0;
50161911d4ff70132fa21c5ee7a987303479e8ef6aeMarc Blank                }
502c263810b08943541135a24e2b7520692152455ccMarc Blank                // The next four values have hard limits which cannot be supported if exceeded.
503c263810b08943541135a24e2b7520692152455ccMarc Blank                if (minPasswordLength > PASSWORD_LENGTH_MAX) {
504c263810b08943541135a24e2b7520692152455ccMarc Blank                    throw new IllegalArgumentException("password length");
505c263810b08943541135a24e2b7520692152455ccMarc Blank                }
5061ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                if (passwordExpirationDays > PASSWORD_EXPIRATION_MAX) {
507c263810b08943541135a24e2b7520692152455ccMarc Blank                    throw new IllegalArgumentException("password expiration");
508c263810b08943541135a24e2b7520692152455ccMarc Blank                }
509c263810b08943541135a24e2b7520692152455ccMarc Blank                if (passwordHistory > PASSWORD_HISTORY_MAX) {
510c263810b08943541135a24e2b7520692152455ccMarc Blank                    throw new IllegalArgumentException("password history");
511c263810b08943541135a24e2b7520692152455ccMarc Blank                }
512c263810b08943541135a24e2b7520692152455ccMarc Blank                if (passwordComplexChars > PASSWORD_COMPLEX_CHARS_MAX) {
513c263810b08943541135a24e2b7520692152455ccMarc Blank                    throw new IllegalArgumentException("complex chars");
514c263810b08943541135a24e2b7520692152455ccMarc Blank                }
515c263810b08943541135a24e2b7520692152455ccMarc Blank                // This value can be reduced (which actually increases security) if necessary
516c263810b08943541135a24e2b7520692152455ccMarc Blank                if (maxPasswordFails > PASSWORD_MAX_FAILS_MAX) {
517c263810b08943541135a24e2b7520692152455ccMarc Blank                    maxPasswordFails = PASSWORD_MAX_FAILS_MAX;
518c263810b08943541135a24e2b7520692152455ccMarc Blank                }
519c263810b08943541135a24e2b7520692152455ccMarc Blank                // This value can be reduced (which actually increases security) if necessary
520c263810b08943541135a24e2b7520692152455ccMarc Blank                if (maxScreenLockTime > SCREEN_LOCK_TIME_MAX) {
521c263810b08943541135a24e2b7520692152455ccMarc Blank                    maxScreenLockTime = SCREEN_LOCK_TIME_MAX;
522c263810b08943541135a24e2b7520692152455ccMarc Blank                }
523345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler            }
524345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler            mMinPasswordLength = minPasswordLength;
525345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler            mPasswordMode = passwordMode;
526345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler            mMaxPasswordFails = maxPasswordFails;
527345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler            mMaxScreenLockTime = maxScreenLockTime;
528345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler            mRequireRemoteWipe = requireRemoteWipe;
5291ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            mPasswordExpirationDays = passwordExpirationDays;
5309b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank            mPasswordHistory = passwordHistory;
5319b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank            mPasswordComplexChars = passwordComplexChars;
532345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        }
533345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler
534345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        /**
535345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler         * Create from values encoded in an account
536345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler         * @param account
537345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler         */
538345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        public PolicySet(Account account) {
539345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler            this(account.mSecurityFlags);
540345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        }
541345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler
542345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        /**
543345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler         * Create from values encoded in an account flags int
544345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler         */
5459b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank        public PolicySet(long flags) {
546345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler            mMinPasswordLength =
5479b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank                (int) ((flags & PASSWORD_LENGTH_MASK) >> PASSWORD_LENGTH_SHIFT);
548345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler            mPasswordMode =
5499b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank                (int) (flags & PASSWORD_MODE_MASK);
550345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler            mMaxPasswordFails =
5519b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank                (int) ((flags & PASSWORD_MAX_FAILS_MASK) >> PASSWORD_MAX_FAILS_SHIFT);
552345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler            mMaxScreenLockTime =
5539b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank                (int) ((flags & SCREEN_LOCK_TIME_MASK) >> SCREEN_LOCK_TIME_SHIFT);
554345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler            mRequireRemoteWipe = 0 != (flags & REQUIRE_REMOTE_WIPE);
5551ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            mPasswordExpirationDays =
5569b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank                (int) ((flags & PASSWORD_EXPIRATION_MASK) >> PASSWORD_EXPIRATION_SHIFT);
5579b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank            mPasswordHistory =
5589b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank                (int) ((flags & PASSWORD_HISTORY_MASK) >> PASSWORD_HISTORY_SHIFT);
5599b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank            mPasswordComplexChars =
5609b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank                (int) ((flags & PASSWORD_COMPLEX_CHARS_MASK) >> PASSWORD_COMPLEX_CHARS_SHIFT);
561345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        }
562345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler
563345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        /**
5643d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler         * Helper to map our internal encoding to DevicePolicyManager password modes.
565d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler         */
5663d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler        public int getDPManagerPasswordQuality() {
567d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler            switch (mPasswordMode) {
568d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler                case PASSWORD_MODE_SIMPLE:
569d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler                    return DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
570d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler                case PASSWORD_MODE_STRONG:
571d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler                    return DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
572d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler                default:
573d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler                    return DevicePolicyManager .PASSWORD_QUALITY_UNSPECIFIED;
574d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler            }
575d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler        }
576d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler
577d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler        /**
5781ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler         * Helper to map expiration times to the millisecond values used by DevicePolicyManager.
5791ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler         */
5801ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        public long getDPManagerPasswordExpirationTimeout() {
5811ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            long result = mPasswordExpirationDays * DAYS_TO_MSEC;
5821ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            // Add a small offset to the password expiration.  This makes it easier to test
5831ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            // by changing (for example) 1 day to 1 day + 5 minutes.  If you set an expiration
5841ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            // that is within the warning period, you should get a warning fairly quickly.
5851ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            if (result > 0) {
5861ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                result += EXPIRATION_OFFSET_MSEC;
5871ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            }
5881ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            return result;
5891ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        }
5901ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler
5911ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        /**
592345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler         * Record flags (and a sync key for the flags) into an Account
593345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler         * Note: the hash code is defined as the encoding used in Account
5943d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler         *
595345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler         * @param account to write the values mSecurityFlags and mSecuritySyncKey
596345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler         * @param syncKey the value to write into the account's mSecuritySyncKey
5973d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler         * @param update if true, also writes the account back to the provider (updating only
5983d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler         *  the fields changed by this API)
5993d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler         * @param context a context for writing to the provider
6003d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler         * @return true if the actual policies changed, false if no change (note, sync key
6013d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler         *  does not affect this)
602345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler         */
6033d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler        public boolean writeAccount(Account account, String syncKey, boolean update,
6043d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler                Context context) {
6059b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank            long newFlags = getSecurityCode();
6063d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler            boolean dirty = (newFlags != account.mSecurityFlags);
6073d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler            account.mSecurityFlags = newFlags;
6083d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler            account.mSecuritySyncKey = syncKey;
6093d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler            if (update) {
6103d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler                if (account.isSaved()) {
6113d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler                    ContentValues cv = new ContentValues();
6123d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler                    cv.put(AccountColumns.SECURITY_FLAGS, account.mSecurityFlags);
6133d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler                    cv.put(AccountColumns.SECURITY_SYNC_KEY, account.mSecuritySyncKey);
6143d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler                    account.update(context, cv);
6153d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler                } else {
6163d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler                    account.save(context);
6173d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler                }
6183d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler            }
6193d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler            return dirty;
620345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        }
621345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler
622345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        @Override
623345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        public boolean equals(Object o) {
624345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler            if (o instanceof PolicySet) {
625345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler                PolicySet other = (PolicySet)o;
6269b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank                return (this.getSecurityCode() == other.getSecurityCode());
627345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler            }
628345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler            return false;
629345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        }
630345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler
631d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank        /**
632d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank         * Supports Parcelable
633d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank         */
634d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank        public int describeContents() {
635d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank            return 0;
636d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank        }
637d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank
638d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank        /**
639d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank         * Supports Parcelable
640d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank         */
641d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank        public static final Parcelable.Creator<PolicySet> CREATOR
642d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank                = new Parcelable.Creator<PolicySet>() {
643d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank            public PolicySet createFromParcel(Parcel in) {
644d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank                return new PolicySet(in);
645d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank            }
646d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank
647d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank            public PolicySet[] newArray(int size) {
648d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank                return new PolicySet[size];
649d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank            }
650d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank        };
651d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank
652d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank        /**
653d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank         * Supports Parcelable
654d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank         */
655d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank        public void writeToParcel(Parcel dest, int flags) {
656d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank            dest.writeInt(mMinPasswordLength);
657d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank            dest.writeInt(mPasswordMode);
658d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank            dest.writeInt(mMaxPasswordFails);
659d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank            dest.writeInt(mMaxScreenLockTime);
660d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank            dest.writeInt(mRequireRemoteWipe ? 1 : 0);
6611ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            dest.writeInt(mPasswordExpirationDays);
662d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank            dest.writeInt(mPasswordHistory);
663d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank            dest.writeInt(mPasswordComplexChars);
664d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank        }
665d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank
666d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank        /**
667d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank         * Supports Parcelable
668d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank         */
669d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank        public PolicySet(Parcel in) {
670d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank            mMinPasswordLength = in.readInt();
671d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank            mPasswordMode = in.readInt();
672d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank            mMaxPasswordFails = in.readInt();
673d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank            mMaxScreenLockTime = in.readInt();
674d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank            mRequireRemoteWipe = in.readInt() == 1;
6751ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            mPasswordExpirationDays = in.readInt();
676d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank            mPasswordHistory = in.readInt();
677d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank            mPasswordComplexChars = in.readInt();
678d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank        }
679d6d874f8c6ce2580ef9ec2406fe411af45b2d92dMarc Blank
680345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        @Override
681345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        public int hashCode() {
6829b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank            long code = getSecurityCode();
6839b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank            return (int) code;
6849b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank        }
6859b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank
6869b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank        public long getSecurityCode() {
6879b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank            long flags = 0;
6889b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank            flags = (long)mMinPasswordLength << PASSWORD_LENGTH_SHIFT;
689345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler            flags |= mPasswordMode;
6909b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank            flags |= (long)mMaxPasswordFails << PASSWORD_MAX_FAILS_SHIFT;
6919b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank            flags |= (long)mMaxScreenLockTime << SCREEN_LOCK_TIME_SHIFT;
692345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler            if (mRequireRemoteWipe) {
693345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler                flags |= REQUIRE_REMOTE_WIPE;
694345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler            }
6959b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank            flags |= (long)mPasswordHistory << PASSWORD_HISTORY_SHIFT;
6961ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            flags |= (long)mPasswordExpirationDays << PASSWORD_EXPIRATION_SHIFT;
6979b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank            flags |= (long)mPasswordComplexChars << PASSWORD_COMPLEX_CHARS_SHIFT;
698345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler            return flags;
699345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        }
700345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler
701345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        @Override
702345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        public String toString() {
703345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler            return "{ " + "pw-len-min=" + mMinPasswordLength + " pw-mode=" + mPasswordMode
704345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler                    + " pw-fails-max=" + mMaxPasswordFails + " screenlock-max="
7059b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank                    + mMaxScreenLockTime + " remote-wipe-req=" + mRequireRemoteWipe
7061ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                    + " pw-expiration=" + mPasswordExpirationDays
7079b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank                    + " pw-history=" + mPasswordHistory
7089b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank                    + " pw-complex-chars=" + mPasswordComplexChars + "}";
709345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        }
710345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    }
711345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler
712345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    /**
7133d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     * If we are not the active device admin, try to become so.
7143d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     *
715e7f4d3ebfcf497c015ba65be7ecebea8926b995cAndy Stadler     * Also checks for any policies that we have added during the lifetime of this app.
716e7f4d3ebfcf497c015ba65be7ecebea8926b995cAndy Stadler     * This catches the case where the user granted an earlier (smaller) set of policies
717e7f4d3ebfcf497c015ba65be7ecebea8926b995cAndy Stadler     * but an app upgrade requires that new policies be granted.
718e7f4d3ebfcf497c015ba65be7ecebea8926b995cAndy Stadler     *
7193d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     * @return true if we are already active, false if we are not
720345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     */
7213d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler    public boolean isActiveAdmin() {
7223d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler        DevicePolicyManager dpm = getDPM();
723e7f4d3ebfcf497c015ba65be7ecebea8926b995cAndy Stadler        return dpm.isAdminActive(mAdminName) &&
724e7f4d3ebfcf497c015ba65be7ecebea8926b995cAndy Stadler                dpm.hasGrantedPolicy(mAdminName, DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD);
7253d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler    }
726345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler
7273d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler    /**
7283d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     * Report admin component name - for making calls into device policy manager
7293d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     */
7303d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler    public ComponentName getAdminComponent() {
7313d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler        return mAdminName;
7323d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler    }
7333d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler
7343d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler    /**
73502d59d21949a77c60859b615312f02e6d8003490Marc Blank     * Delete all accounts whose security flags aren't zero (i.e. they have security enabled).
73602d59d21949a77c60859b615312f02e6d8003490Marc Blank     * This method is synchronous, so it should normally be called within a worker thread (the
73702d59d21949a77c60859b615312f02e6d8003490Marc Blank     * exception being for unit tests)
73802d59d21949a77c60859b615312f02e6d8003490Marc Blank     *
73902d59d21949a77c60859b615312f02e6d8003490Marc Blank     * @param context the caller's context
74002d59d21949a77c60859b615312f02e6d8003490Marc Blank     */
74102d59d21949a77c60859b615312f02e6d8003490Marc Blank    /*package*/ void deleteSecuredAccounts(Context context) {
74202d59d21949a77c60859b615312f02e6d8003490Marc Blank        ContentResolver cr = context.getContentResolver();
74302d59d21949a77c60859b615312f02e6d8003490Marc Blank        // Find all accounts with security and delete them
74402d59d21949a77c60859b615312f02e6d8003490Marc Blank        Cursor c = cr.query(Account.CONTENT_URI, EmailContent.ID_PROJECTION,
74502d59d21949a77c60859b615312f02e6d8003490Marc Blank                AccountColumns.SECURITY_FLAGS + "!=0", null, null);
74602d59d21949a77c60859b615312f02e6d8003490Marc Blank        try {
74702d59d21949a77c60859b615312f02e6d8003490Marc Blank            Log.w(TAG, "Email administration disabled; deleting " + c.getCount() +
74802d59d21949a77c60859b615312f02e6d8003490Marc Blank                    " secured account(s)");
74902d59d21949a77c60859b615312f02e6d8003490Marc Blank            while (c.moveToNext()) {
75002d59d21949a77c60859b615312f02e6d8003490Marc Blank                Controller.getInstance(context).deleteAccountSync(
75102d59d21949a77c60859b615312f02e6d8003490Marc Blank                        c.getLong(EmailContent.ID_PROJECTION_COLUMN), context);
75202d59d21949a77c60859b615312f02e6d8003490Marc Blank            }
75302d59d21949a77c60859b615312f02e6d8003490Marc Blank        } finally {
75402d59d21949a77c60859b615312f02e6d8003490Marc Blank            c.close();
75502d59d21949a77c60859b615312f02e6d8003490Marc Blank        }
75602d59d21949a77c60859b615312f02e6d8003490Marc Blank        updatePolicies(-1);
75702d59d21949a77c60859b615312f02e6d8003490Marc Blank    }
75802d59d21949a77c60859b615312f02e6d8003490Marc Blank
75902d59d21949a77c60859b615312f02e6d8003490Marc Blank    /**
76002d59d21949a77c60859b615312f02e6d8003490Marc Blank     * Internal handler for enabled->disabled transitions.  Deletes all secured accounts.
761a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler     * Must call from worker thread, not on UI thread.
7623d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     */
76302d59d21949a77c60859b615312f02e6d8003490Marc Blank    /*package*/ void onAdminEnabled(boolean isEnabled) {
764856e09d76ab62272e660fd4a08e25637f17319a0Andrew Stadler        if (!isEnabled) {
765a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler            deleteSecuredAccounts(mContext);
7663d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler        }
7673d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler    }
7683d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler
7693d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler    /**
7701ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * Handle password expiration - if any accounts appear to have triggered this, put up
7711ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * warnings, or even shut them down.
7721ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     *
7731ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * NOTE:  If there are multiple accounts with password expiration policies, the device
7741ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * password will be set to expire in the shortest required interval (most secure).  The logic
7751ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * in this method operates based on the aggregate setting - irrespective of which account caused
7761ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * the expiration.  In other words, all accounts (that require expiration) will run/stop
7771ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * based on the requirements of the account with the shortest interval.
7781ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     */
779a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler    private void onPasswordExpiring(Context context) {
7801ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        // 1.  Do we have any accounts that matter here?
7811ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        long nextExpiringAccountId = findShortestExpiration(context);
7821ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler
7831ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        // 2.  If not, exit immediately
7841ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        if (nextExpiringAccountId == -1) {
7851ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            return;
7861ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        }
7871ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler
7881ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        // 3.  If yes, are we warning or expired?
7891ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        long expirationDate = getDPM().getPasswordExpiration(mAdminName);
7901ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        long timeUntilExpiration = expirationDate - System.currentTimeMillis();
7911ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        boolean expired = timeUntilExpiration < 0;
7921ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        if (!expired) {
7931ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            // 4.  If warning, simply put up a generic notification and report that it came from
7941ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            // the shortest-expiring account.
7951ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            Account account = Account.restoreAccountWithId(context, nextExpiringAccountId);
7961ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            if (account == null) return;
7971ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD);
7981ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            String ticker = context.getString(
7991ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                    R.string.password_expire_warning_ticker_fmt, account.getDisplayName());
8001ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            String contentTitle = context.getString(
8011ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                    R.string.password_expire_warning_content_title);
8021ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            String contentText = context.getString(
8031ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                    R.string.password_expire_warning_content_text_fmt, account.getDisplayName());
8041ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            NotificationController nc = NotificationController.getInstance(mContext);
8051ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            nc.postAccountNotification(account, ticker, contentTitle, contentText, intent,
8061ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                    NotificationController.NOTIFICATION_ID_PASSWORD_EXPIRING);
8071ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        } else {
8081ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            // 5.  Actually expired - find all accounts that expire passwords, and wipe them
8091ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            boolean wiped = wipeExpiredAccounts(context, Controller.getInstance(context));
8101ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            if (wiped) {
8111ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                // Post notification
8121ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                Account account = Account.restoreAccountWithId(context, nextExpiringAccountId);
8131ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                if (account == null) return;
8141ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                Intent intent =
8151ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                    new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD);
8161ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                String ticker = context.getString(R.string.password_expired_ticker);
8171ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                String contentTitle = context.getString(R.string.password_expired_content_title);
8181ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                String contentText = context.getString(R.string.password_expired_content_text);
8191ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                NotificationController nc = NotificationController.getInstance(mContext);
8201ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                nc.postAccountNotification(account, ticker, contentTitle,
8211ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                        contentText, intent,
8221ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                        NotificationController.NOTIFICATION_ID_PASSWORD_EXPIRED);
8231ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            }
8241ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        }
8251ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler    }
8261ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler
8271ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler    /**
8281ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * Find the account with the shortest expiration time.  This is always assumed to be
8291ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * the account that forces the password to be refreshed.
8301ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * @return -1 if no expirations, or accountId if one is found
8311ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     */
8321ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler    /* package */ static long findShortestExpiration(Context context) {
8331ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        long nextExpiringAccountId = -1;
8341ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        long shortestExpiration = Long.MAX_VALUE;
8351ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        Cursor c = context.getContentResolver().query(Account.CONTENT_URI,
8361ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                ACCOUNT_SECURITY_PROJECTION, Account.SECURITY_NONZERO_SELECTION, null, null);
8371ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        try {
8381ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            while (c.moveToNext()) {
8391ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                long flags = c.getLong(ACCOUNT_SECURITY_COLUMN_FLAGS);
8401ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                if (flags != 0) {
8411ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                    PolicySet p = new PolicySet(flags);
8421ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                    if (p.mPasswordExpirationDays > 0 &&
8431ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                            p.mPasswordExpirationDays < shortestExpiration) {
8441ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                        nextExpiringAccountId = c.getLong(ACCOUNT_SECURITY_COLUMN_ID);
8451ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                        shortestExpiration = p.mPasswordExpirationDays;
8461ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                    }
8471ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                }
8481ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            }
8491ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        } finally {
8501ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            c.close();
8511ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        }
8521ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        return nextExpiringAccountId;
8531ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler    }
8541ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler
8551ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler    /**
8561ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * For all accounts that require password expiration, put them in security hold and wipe
8571ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * their data.
8581ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * @param context
8591ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * @param controller
8601ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * @return true if one or more accounts were wiped
8611ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     */
8621ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler    /* package */ static boolean wipeExpiredAccounts(Context context, Controller controller) {
8631ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        boolean result = false;
8641ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        Cursor c = context.getContentResolver().query(Account.CONTENT_URI,
8651ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                ACCOUNT_SECURITY_PROJECTION, Account.SECURITY_NONZERO_SELECTION, null, null);
8661ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        try {
8671ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            while (c.moveToNext()) {
8681ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                long flags = c.getLong(ACCOUNT_SECURITY_COLUMN_FLAGS);
8691ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                if (flags != 0) {
8701ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                    PolicySet p = new PolicySet(flags);
8711ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                    if (p.mPasswordExpirationDays > 0) {
8721ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                        long accountId = c.getLong(ACCOUNT_SECURITY_COLUMN_ID);
8731ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                        Account account = Account.restoreAccountWithId(context, accountId);
8741ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                        if (account != null) {
8751ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                            // Mark the account as "on hold".
8761ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                            setAccountHoldFlag(context, account, true);
8771ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                            // Erase data
8781ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                            controller.deleteSyncedDataSync(accountId);
8791ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                            // Report one or more were found
8801ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                            result = true;
8811ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                        }
8821ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                    }
8831ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                }
8841ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            }
8851ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        } finally {
8861ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            c.close();
8871ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        }
8881ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        return result;
8891ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler    }
8901ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler
8911ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler    /**
892a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler     * Callback from EmailBroadcastProcessorService.  This provides the workers for the
893a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler     * DeviceAdminReceiver calls.  These should perform the work directly and not use async
894a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler     * threads for completion.
895a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler     */
896a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler    public static void onDeviceAdminReceiverMessage(Context context, int message) {
897a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler        SecurityPolicy instance = SecurityPolicy.getInstance(context);
898a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler        switch (message) {
899a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler            case DEVICE_ADMIN_MESSAGE_ENABLED:
900a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler                instance.onAdminEnabled(true);
901a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler                break;
902a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler            case DEVICE_ADMIN_MESSAGE_DISABLED:
903a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler                instance.onAdminEnabled(false);
904a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler                break;
905a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler            case DEVICE_ADMIN_MESSAGE_PASSWORD_CHANGED:
906a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler                // TODO make a small helper for this
907a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler                // Clear security holds (if any)
908a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler                Account.clearSecurityHoldOnAllAccounts(context);
909a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler                // Cancel any active notifications (if any are posted)
910a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler                NotificationController nc = NotificationController.getInstance(context);
911a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler                nc.cancelNotification(NotificationController.NOTIFICATION_ID_PASSWORD_EXPIRING);
912a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler                nc.cancelNotification(NotificationController.NOTIFICATION_ID_PASSWORD_EXPIRED);
913a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler                break;
914a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler            case DEVICE_ADMIN_MESSAGE_PASSWORD_EXPIRING:
915a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler                instance.onPasswordExpiring(instance.mContext);
916a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler                break;
917a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler        }
918a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler    }
919a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler
920a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler    /**
9213d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     * Device Policy administrator.  This is primarily a listener for device state changes.
9223d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     * Note:  This is instantiated by incoming messages.
923a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler     * Note:  This is actually a BroadcastReceiver and must remain within the guidelines required
924a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler     *        for proper behavior, including avoidance of ANRs.
9255893e9e008d46cf4be0c7f709a6e77e2652c3dddAndrew Stadler     * Note:  We do not implement onPasswordFailed() because the default behavior of the
9265893e9e008d46cf4be0c7f709a6e77e2652c3dddAndrew Stadler     *        DevicePolicyManager - complete local wipe after 'n' failures - is sufficient.
9273d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     */
9284ae83c58b3e136b4b1e859ee304ad1b332e9597fDianne Hackborn    public static class PolicyAdmin extends DeviceAdminReceiver {
929345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler
930345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        /**
931345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler         * Called after the administrator is first enabled.
932345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler         */
933345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        @Override
934345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        public void onEnabled(Context context, Intent intent) {
935a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler            EmailBroadcastProcessorService.processDevicePolicyMessage(context,
936a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler                    DEVICE_ADMIN_MESSAGE_ENABLED);
937345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        }
938856e09d76ab62272e660fd4a08e25637f17319a0Andrew Stadler
939345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        /**
940345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler         * Called prior to the administrator being disabled.
941345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler         */
942345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        @Override
943345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        public void onDisabled(Context context, Intent intent) {
944a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler            EmailBroadcastProcessorService.processDevicePolicyMessage(context,
945a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler                    DEVICE_ADMIN_MESSAGE_DISABLED);
946345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        }
947856e09d76ab62272e660fd4a08e25637f17319a0Andrew Stadler
948345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        /**
94902d59d21949a77c60859b615312f02e6d8003490Marc Blank         * Called when the user asks to disable administration; we return a warning string that
95002d59d21949a77c60859b615312f02e6d8003490Marc Blank         * will be presented to the user
95102d59d21949a77c60859b615312f02e6d8003490Marc Blank         */
95202d59d21949a77c60859b615312f02e6d8003490Marc Blank        @Override
95302d59d21949a77c60859b615312f02e6d8003490Marc Blank        public CharSequence onDisableRequested(Context context, Intent intent) {
95402d59d21949a77c60859b615312f02e6d8003490Marc Blank            return context.getString(R.string.disable_admin_warning);
95502d59d21949a77c60859b615312f02e6d8003490Marc Blank        }
95602d59d21949a77c60859b615312f02e6d8003490Marc Blank
95702d59d21949a77c60859b615312f02e6d8003490Marc Blank        /**
958345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler         * Called after the user has changed their password.
959345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler         */
960345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        @Override
961345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        public void onPasswordChanged(Context context, Intent intent) {
962a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler            EmailBroadcastProcessorService.processDevicePolicyMessage(context,
963a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler                    DEVICE_ADMIN_MESSAGE_PASSWORD_CHANGED);
9641ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        }
9651ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler
9661ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        /**
9671ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler         * Called when device password is expiring
9681ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler         */
9691ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        @Override
9701ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        public void onPasswordExpiring(Context context, Intent intent) {
971a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler            EmailBroadcastProcessorService.processDevicePolicyMessage(context,
972a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler                    DEVICE_ADMIN_MESSAGE_PASSWORD_EXPIRING);
973345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        }
974345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    }
975345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler}
976