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
19e7f4d3ebfcf497c015ba65be7ecebea8926b995cAndy Stadlerimport android.app.admin.DeviceAdminInfo;
206d0016229adc13fefe68820fe4d6e46f530952baDianne Hackbornimport android.app.admin.DeviceAdminReceiver;
216d0016229adc13fefe68820fe4d6e46f530952baDianne Hackbornimport android.app.admin.DevicePolicyManager;
22d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadlerimport android.content.ComponentName;
23f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blankimport android.content.ContentProviderOperation;
2402d59d21949a77c60859b615312f02e6d8003490Marc Blankimport android.content.ContentResolver;
25f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blankimport android.content.ContentUris;
263d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadlerimport android.content.ContentValues;
27345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadlerimport android.content.Context;
28345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadlerimport android.content.Intent;
29f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blankimport android.content.OperationApplicationException;
30345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadlerimport android.database.Cursor;
31f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blankimport android.net.Uri;
32bd1e0b79e470afcfb9e6281711ad1038050c38fbYu Ping Huimport android.os.Bundle;
33f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blankimport android.os.RemoteException;
34345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler
35bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrookimport com.android.email.NotificationController;
36bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrookimport com.android.email.NotificationControllerCreatorHolder;
37a60550e0eb08e0239d1fcea261b37ba592a35ba4Yu Ping Huimport com.android.email.provider.AccountReconciler;
38f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blankimport com.android.email.provider.EmailProvider;
39c6df1d605fb3e235df6d4a21ae00632c9d6e3cc2Marc Blankimport com.android.email.service.EmailBroadcastProcessorService;
40bd1e0b79e470afcfb9e6281711ad1038050c38fbYu Ping Huimport com.android.email.service.EmailServiceUtils;
41c6df1d605fb3e235df6d4a21ae00632c9d6e3cc2Marc Blankimport com.android.emailcommon.Logging;
42c6df1d605fb3e235df6d4a21ae00632c9d6e3cc2Marc Blankimport com.android.emailcommon.provider.Account;
43c6df1d605fb3e235df6d4a21ae00632c9d6e3cc2Marc Blankimport com.android.emailcommon.provider.EmailContent;
44c6df1d605fb3e235df6d4a21ae00632c9d6e3cc2Marc Blankimport com.android.emailcommon.provider.EmailContent.AccountColumns;
45c6df1d605fb3e235df6d4a21ae00632c9d6e3cc2Marc Blankimport com.android.emailcommon.provider.EmailContent.PolicyColumns;
46c6df1d605fb3e235df6d4a21ae00632c9d6e3cc2Marc Blankimport com.android.emailcommon.provider.Policy;
47f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blankimport com.android.emailcommon.utility.TextUtilities;
48c6df1d605fb3e235df6d4a21ae00632c9d6e3cc2Marc Blankimport com.android.emailcommon.utility.Utility;
49560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedyimport com.android.mail.utils.LogUtils;
50c6df1d605fb3e235df6d4a21ae00632c9d6e3cc2Marc Blankimport com.google.common.annotations.VisibleForTesting;
51c6df1d605fb3e235df6d4a21ae00632c9d6e3cc2Marc Blank
52f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blankimport java.util.ArrayList;
53f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank
54345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler/**
55d71d0b223a5cd02e2a8f1ec5c3f8cebab170d65fAndrew Stadler * Utility functions to support reading and writing security policies, and handshaking the device
56d71d0b223a5cd02e2a8f1ec5c3f8cebab170d65fAndrew Stadler * into and out of various security states.
57345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler */
58345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadlerpublic class SecurityPolicy {
59ce6916b32a98a568ceafb734d050801f4459a532Martin Hibdon    private static final String TAG = "Email";
60345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    private static SecurityPolicy sInstance = null;
61345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    private Context mContext;
62d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler    private DevicePolicyManager mDPM;
63d09cff08882e553afce919865a2cc60b657d4659Ben Komalo    private final ComponentName mAdminName;
64aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank    private Policy mAggregatePolicy;
652a5eeea9213005060256054ec773e72406415ce4Andrew Stadler
66a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler    // Messages used for DevicePolicyManager callbacks
67a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler    private static final int DEVICE_ADMIN_MESSAGE_ENABLED = 1;
68a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler    private static final int DEVICE_ADMIN_MESSAGE_DISABLED = 2;
69a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler    private static final int DEVICE_ADMIN_MESSAGE_PASSWORD_CHANGED = 3;
70a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler    private static final int DEVICE_ADMIN_MESSAGE_PASSWORD_EXPIRING = 4;
71a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler
72aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank    private static final String HAS_PASSWORD_EXPIRATION =
73aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank        PolicyColumns.PASSWORD_EXPIRATION_DAYS + ">0";
74aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank
75345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    /**
76345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     * Get the security policy instance
77345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     */
78345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    public synchronized static SecurityPolicy getInstance(Context context) {
79345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        if (sInstance == null) {
80a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler            sInstance = new SecurityPolicy(context.getApplicationContext());
81345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        }
82345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        return sInstance;
83345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    }
84345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler
85345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    /**
86345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     * Private constructor (one time only)
87345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     */
88345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    private SecurityPolicy(Context context) {
89968be441b4c253668c4ee1c7a3f8e4b0eb12cf24Makoto Onuki        mContext = context.getApplicationContext();
90d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler        mDPM = null;
91d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler        mAdminName = new ComponentName(context, PolicyAdmin.class);
92d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler        mAggregatePolicy = null;
93345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    }
94345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler
95345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    /**
96345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     * For testing only: Inject context into already-created instance
97345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     */
98345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    /* package */ void setContext(Context context) {
99345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        mContext = context;
100345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    }
101345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler
102345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    /**
103345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     * Compute the aggregate policy for all accounts that require it, and record it.
104345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     *
105345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     * The business logic is as follows:
106345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     *  min password length         take the max
107345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     *  password mode               take the max (strongest mode)
108345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     *  max password fails          take the min
109345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     *  max screen lock time        take the min
110345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     *  require remote wipe         take the max (logical or)
1119b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank     *  password history            take the max (strongest mode)
1121ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     *  password expiration         take the min (strongest mode)
1139b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank     *  password complex chars      take the max (strongest mode)
114469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler     *  encryption                  take the max (logical or)
115968be441b4c253668c4ee1c7a3f8e4b0eb12cf24Makoto Onuki     *
116d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler     * @return a policy representing the strongest aggregate.  If no policy sets are defined,
117d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler     * a lightweight "nothing required" policy will be returned.  Never null.
118345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     */
119d09cff08882e553afce919865a2cc60b657d4659Ben Komalo    @VisibleForTesting
120d09cff08882e553afce919865a2cc60b657d4659Ben Komalo    Policy computeAggregatePolicy() {
121345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        boolean policiesFound = false;
122aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank        Policy aggregate = new Policy();
123aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank        aggregate.mPasswordMinLength = Integer.MIN_VALUE;
124aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank        aggregate.mPasswordMode = Integer.MIN_VALUE;
125aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank        aggregate.mPasswordMaxFails = Integer.MAX_VALUE;
126aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank        aggregate.mPasswordHistory = Integer.MIN_VALUE;
127aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank        aggregate.mPasswordExpirationDays = Integer.MAX_VALUE;
128aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank        aggregate.mPasswordComplexChars = Integer.MIN_VALUE;
129aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank        aggregate.mMaxScreenLockTime = Integer.MAX_VALUE;
130aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank        aggregate.mRequireRemoteWipe = false;
131aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank        aggregate.mRequireEncryption = false;
132e76962b1b9c66ecc3fd49cd4c956f234365bfe5cBen Komalo
133e76962b1b9c66ecc3fd49cd4c956f234365bfe5cBen Komalo        // This can never be supported at this time. It exists only for historic reasons where
134e76962b1b9c66ecc3fd49cd4c956f234365bfe5cBen Komalo        // this was able to be supported prior to the introduction of proper removable storage
135e76962b1b9c66ecc3fd49cd4c956f234365bfe5cBen Komalo        // support for external storage.
136aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank        aggregate.mRequireEncryptionExternal = false;
137aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank
138aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank        Cursor c = mContext.getContentResolver().query(Policy.CONTENT_URI,
139aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                Policy.CONTENT_PROJECTION, null, null, null);
140aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank        Policy policy = new Policy();
141345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        try {
142345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler            while (c.moveToNext()) {
143aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                policy.restore(c);
14451c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon                if (DebugUtils.DEBUG) {
145560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy                    LogUtils.d(TAG, "Aggregate from: " + policy);
146aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                }
147aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                aggregate.mPasswordMinLength =
148aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                    Math.max(policy.mPasswordMinLength, aggregate.mPasswordMinLength);
149aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                aggregate.mPasswordMode  = Math.max(policy.mPasswordMode, aggregate.mPasswordMode);
150aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                if (policy.mPasswordMaxFails > 0) {
151aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                    aggregate.mPasswordMaxFails =
152aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                        Math.min(policy.mPasswordMaxFails, aggregate.mPasswordMaxFails);
153aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                }
154aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                if (policy.mMaxScreenLockTime > 0) {
155aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                    aggregate.mMaxScreenLockTime = Math.min(policy.mMaxScreenLockTime,
156aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                            aggregate.mMaxScreenLockTime);
157aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                }
158aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                if (policy.mPasswordHistory > 0) {
159aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                    aggregate.mPasswordHistory =
160aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                        Math.max(policy.mPasswordHistory, aggregate.mPasswordHistory);
161345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler                }
162aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                if (policy.mPasswordExpirationDays > 0) {
163aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                    aggregate.mPasswordExpirationDays =
164aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                        Math.min(policy.mPasswordExpirationDays, aggregate.mPasswordExpirationDays);
165aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                }
166aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                if (policy.mPasswordComplexChars > 0) {
167aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                    aggregate.mPasswordComplexChars = Math.max(policy.mPasswordComplexChars,
168aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                            aggregate.mPasswordComplexChars);
169aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                }
170aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                aggregate.mRequireRemoteWipe |= policy.mRequireRemoteWipe;
171aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                aggregate.mRequireEncryption |= policy.mRequireEncryption;
172d09cff08882e553afce919865a2cc60b657d4659Ben Komalo                aggregate.mDontAllowCamera |= policy.mDontAllowCamera;
173aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                policiesFound = true;
174345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler            }
175345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        } finally {
176345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler            c.close();
177345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        }
178345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        if (policiesFound) {
1793d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler            // final cleanup pass converts any untouched min/max values to zero (not specified)
180aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank            if (aggregate.mPasswordMinLength == Integer.MIN_VALUE) aggregate.mPasswordMinLength = 0;
181aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank            if (aggregate.mPasswordMode == Integer.MIN_VALUE) aggregate.mPasswordMode = 0;
182aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank            if (aggregate.mPasswordMaxFails == Integer.MAX_VALUE) aggregate.mPasswordMaxFails = 0;
183aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank            if (aggregate.mMaxScreenLockTime == Integer.MAX_VALUE) aggregate.mMaxScreenLockTime = 0;
184aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank            if (aggregate.mPasswordHistory == Integer.MIN_VALUE) aggregate.mPasswordHistory = 0;
185aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank            if (aggregate.mPasswordExpirationDays == Integer.MAX_VALUE)
186aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                aggregate.mPasswordExpirationDays = 0;
187aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank            if (aggregate.mPasswordComplexChars == Integer.MIN_VALUE)
188aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                aggregate.mPasswordComplexChars = 0;
18951c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon            if (DebugUtils.DEBUG) {
190560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy                LogUtils.d(TAG, "Calculated Aggregate: " + aggregate);
191aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank            }
192aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank            return aggregate;
193345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        }
19451c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon        if (DebugUtils.DEBUG) {
195560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy            LogUtils.d(TAG, "Calculated Aggregate: no policy");
196aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank        }
197aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank        return Policy.NO_POLICY;
198345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    }
199345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler
200345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    /**
20150d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler     * Return updated aggregate policy, from cached value if possible
20250d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler     */
203aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank    public synchronized Policy getAggregatePolicy() {
20450d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler        if (mAggregatePolicy == null) {
20550d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler            mAggregatePolicy = computeAggregatePolicy();
20650d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler        }
20750d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler        return mAggregatePolicy;
20850d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler    }
20950d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler
21050d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler    /**
211d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler     * Get the dpm.  This mainly allows us to make some utility calls without it, for testing.
212d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler     */
213a0d080558ff06f88f000cf424803c8241dd8d2ebAndy Stadler    /* package */ synchronized DevicePolicyManager getDPM() {
214d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler        if (mDPM == null) {
215d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler            mDPM = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
216d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler        }
217d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler        return mDPM;
218d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler    }
219d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler
220d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler    /**
221f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     * API: Report that policies may have been updated due to rewriting values in an Account; we
222f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     * clear the aggregate policy (so it can be recomputed) and set the policies in the DPM
223d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler     */
224f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    public synchronized void policiesUpdated() {
225d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler        mAggregatePolicy = null;
226f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        setActivePolicies();
227d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler    }
228d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler
229d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler    /**
23050d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler     * API: Report that policies may have been updated *and* the caller vouches that the
23150d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler     * change is a reduction in policies.  This forces an immediate change to device state.
23250d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler     * Typically used when deleting accounts, although we may use it for server-side policy
23350d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler     * rollbacks.
23450d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler     */
23550d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler    public void reducePolicies() {
23651c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon        if (DebugUtils.DEBUG) {
237560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy            LogUtils.d(TAG, "reducePolicies");
238aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank        }
239f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        policiesUpdated();
240a0d080558ff06f88f000cf424803c8241dd8d2ebAndy Stadler    }
241a0d080558ff06f88f000cf424803c8241dd8d2ebAndy Stadler
242a0d080558ff06f88f000cf424803c8241dd8d2ebAndy Stadler    /**
243469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler     * API: Query used to determine if a given policy is "active" (the device is operating at
244469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler     * the required security level).
245469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler     *
246d09cff08882e553afce919865a2cc60b657d4659Ben Komalo     * @param policy the policies requested, or null to check aggregate stored policies
247469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler     * @return true if the requested policies are active, false if not.
248469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler     */
249aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank    public boolean isActive(Policy policy) {
250aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank        int reasons = getInactiveReasons(policy);
25151c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon        if (DebugUtils.DEBUG && (reasons != 0)) {
252aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank            StringBuilder sb = new StringBuilder("isActive for " + policy + ": ");
253076ab8307439f31e8197579f11b9bc3342fd5626Tony Mantler            sb.append("FALSE -> ");
254aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank            if ((reasons & INACTIVE_NEED_ACTIVATION) != 0) {
255aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                sb.append("no_admin ");
256aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank            }
257aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank            if ((reasons & INACTIVE_NEED_CONFIGURATION) != 0) {
258aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                sb.append("config ");
259aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank            }
260aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank            if ((reasons & INACTIVE_NEED_PASSWORD) != 0) {
261aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                sb.append("password ");
262aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank            }
263aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank            if ((reasons & INACTIVE_NEED_ENCRYPTION) != 0) {
264aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                sb.append("encryption ");
265aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank            }
266f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            if ((reasons & INACTIVE_PROTOCOL_POLICIES) != 0) {
267f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                sb.append("protocol ");
268f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            }
269560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy            LogUtils.d(TAG, sb.toString());
270aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank        }
271469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler        return reasons == 0;
272469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler    }
273469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler
274469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler    /**
275469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler     * Return bits from isActive:  Device Policy Manager has not been activated
276469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler     */
277469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler    public final static int INACTIVE_NEED_ACTIVATION = 1;
278469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler
279469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler    /**
280469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler     * Return bits from isActive:  Some required configuration is not correct (no user action).
281469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler     */
282469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler    public final static int INACTIVE_NEED_CONFIGURATION = 2;
283469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler
284469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler    /**
285469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler     * Return bits from isActive:  Password needs to be set or updated
286469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler     */
287469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler    public final static int INACTIVE_NEED_PASSWORD = 4;
288469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler
289469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler    /**
290469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler     * Return bits from isActive:  Encryption has not be enabled
291469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler     */
292469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler    public final static int INACTIVE_NEED_ENCRYPTION = 8;
293469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler
294469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler    /**
295f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     * Return bits from isActive:  Protocol-specific policies cannot be enforced
296f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     */
297f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    public final static int INACTIVE_PROTOCOL_POLICIES = 16;
298f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank
299f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    /**
300d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler     * API: Query used to determine if a given policy is "active" (the device is operating at
3013d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     * the required security level).
302345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     *
3033d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     * This can be used when syncing a specific account, by passing a specific set of policies
3043d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     * for that account.  Or, it can be used at any time to compare the device
3053d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     * state against the aggregate set of device policies stored in all accounts.
3063d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     *
3073d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     * This method is for queries only, and does not trigger any change in device state.
3083d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     *
3091ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * NOTE:  If there are multiple accounts with password expiration policies, the device
3101ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * password will be set to expire in the shortest required interval (most secure).  This method
3111ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * will return 'false' as soon as the password expires - irrespective of which account caused
3121ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * the expiration.  In other words, all accounts (that require expiration) will run/stop
3131ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * based on the requirements of the account with the shortest interval.
3141ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     *
315d09cff08882e553afce919865a2cc60b657d4659Ben Komalo     * @param policy the policies requested, or null to check aggregate stored policies
316469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler     * @return zero if the requested policies are active, non-zero bits indicates that more work
317469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler     * is needed (typically, by the user) before the required security polices are fully active.
318345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     */
319aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank    public int getInactiveReasons(Policy policy) {
32050d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler        // select aggregate set if needed
321aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank        if (policy == null) {
322aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank            policy = getAggregatePolicy();
32350d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler        }
32450d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler        // quick check for the "empty set" of no policies
325aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank        if (policy == Policy.NO_POLICY) {
326469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler            return 0;
32750d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler        }
328469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler        int reasons = 0;
329d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler        DevicePolicyManager dpm = getDPM();
330e7f4d3ebfcf497c015ba65be7ecebea8926b995cAndy Stadler        if (isActiveAdmin()) {
331d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler            // check each policy explicitly
332aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank            if (policy.mPasswordMinLength > 0) {
333aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                if (dpm.getPasswordMinimumLength(mAdminName) < policy.mPasswordMinLength) {
334469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler                    reasons |= INACTIVE_NEED_PASSWORD;
335d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler                }
336d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler            }
337aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank            if (policy.mPasswordMode > 0) {
338aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                if (dpm.getPasswordQuality(mAdminName) < policy.getDPManagerPasswordQuality()) {
339469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler                    reasons |= INACTIVE_NEED_PASSWORD;
340d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler                }
341d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler                if (!dpm.isActivePasswordSufficient()) {
342469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler                    reasons |= INACTIVE_NEED_PASSWORD;
343d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler                }
344d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler            }
345aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank            if (policy.mMaxScreenLockTime > 0) {
346d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler                // Note, we use seconds, dpm uses milliseconds
347aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                if (dpm.getMaximumTimeToLock(mAdminName) > policy.mMaxScreenLockTime * 1000) {
348469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler                    reasons |= INACTIVE_NEED_CONFIGURATION;
349d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler                }
350d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler            }
351aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank            if (policy.mPasswordExpirationDays > 0) {
3521ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                // confirm that expirations are currently set
3531ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                long currentTimeout = dpm.getPasswordExpirationTimeout(mAdminName);
3541ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                if (currentTimeout == 0
355aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                        || currentTimeout > policy.getDPManagerPasswordExpirationTimeout()) {
356469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler                    reasons |= INACTIVE_NEED_PASSWORD;
3571ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                }
3581ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                // confirm that the current password hasn't expired
3591ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                long expirationDate = dpm.getPasswordExpiration(mAdminName);
3601ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                long timeUntilExpiration = expirationDate - System.currentTimeMillis();
3611ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                boolean expired = timeUntilExpiration < 0;
3621ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                if (expired) {
363469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler                    reasons |= INACTIVE_NEED_PASSWORD;
3641ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                }
3659b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank            }
366aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank            if (policy.mPasswordHistory > 0) {
367aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                if (dpm.getPasswordHistoryLength(mAdminName) < policy.mPasswordHistory) {
368e86d8af163ada4d0b3f7c5cb0b32cfeb12da473cMarc Blank                    // There's no user action for changes here; this is just a configuration change
369e86d8af163ada4d0b3f7c5cb0b32cfeb12da473cMarc Blank                    reasons |= INACTIVE_NEED_CONFIGURATION;
3709b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank                }
3719b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank            }
372aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank            if (policy.mPasswordComplexChars > 0) {
373aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                if (dpm.getPasswordMinimumNonLetter(mAdminName) < policy.mPasswordComplexChars) {
374469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler                    reasons |= INACTIVE_NEED_PASSWORD;
375469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler                }
376469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler            }
377aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank            if (policy.mRequireEncryption) {
378c2e638351c19ab22ad9ab4cce2853414c34724c3Andy Stadler                int encryptionStatus = getDPM().getStorageEncryptionStatus();
379469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler                if (encryptionStatus != DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE) {
380469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler                    reasons |= INACTIVE_NEED_ENCRYPTION;
3819b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank                }
3829b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank            }
383ce582527bbdce6e7a5c255123e9f2b743467919eMarc Blank            if (policy.mDontAllowCamera && !dpm.getCameraDisabled(mAdminName)) {
384ce582527bbdce6e7a5c255123e9f2b743467919eMarc Blank                reasons |= INACTIVE_NEED_CONFIGURATION;
385ce582527bbdce6e7a5c255123e9f2b743467919eMarc Blank            }
386d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler            // password failures are counted locally - no test required here
387d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler            // no check required for remote wipe (it's supported, if we're the admin)
3882a5eeea9213005060256054ec773e72406415ce4Andrew Stadler
389f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            if (policy.mProtocolPoliciesUnsupported != null) {
390f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                reasons |= INACTIVE_PROTOCOL_POLICIES;
391f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            }
392f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank
393469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler            // If we made it all the way, reasons == 0 here.  Otherwise it's a list of grievances.
394469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler            return reasons;
395d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler        }
396d71d0b223a5cd02e2a8f1ec5c3f8cebab170d65fAndrew Stadler        // return false, not active
397469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler        return INACTIVE_NEED_ACTIVATION;
398345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    }
399345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler
400345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    /**
40150d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler     * Set the requested security level based on the aggregate set of requests.
40250d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler     * If the set is empty, we release our device administration.  If the set is non-empty,
40350d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler     * we only proceed if we are already active as an admin.
4043d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     */
4053d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler    public void setActivePolicies() {
4063d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler        DevicePolicyManager dpm = getDPM();
40750d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler        // compute aggregate set of policies
408aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank        Policy aggregatePolicy = getAggregatePolicy();
40950d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler        // if empty set, detach from policy manager
410aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank        if (aggregatePolicy == Policy.NO_POLICY) {
41151c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon            if (DebugUtils.DEBUG) {
412560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy                LogUtils.d(TAG, "setActivePolicies: none, remove admin");
413aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank            }
41450d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler            dpm.removeActiveAdmin(mAdminName);
415e7f4d3ebfcf497c015ba65be7ecebea8926b995cAndy Stadler        } else if (isActiveAdmin()) {
41651c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon            if (DebugUtils.DEBUG) {
417560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy                LogUtils.d(TAG, "setActivePolicies: " + aggregatePolicy);
418aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank            }
4193d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler            // set each policy in the policy manager
4203d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler            // password mode & length
421aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank            dpm.setPasswordQuality(mAdminName, aggregatePolicy.getDPManagerPasswordQuality());
422aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank            dpm.setPasswordMinimumLength(mAdminName, aggregatePolicy.mPasswordMinLength);
4233d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler            // screen lock time
424aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank            dpm.setMaximumTimeToLock(mAdminName, aggregatePolicy.mMaxScreenLockTime * 1000);
4253d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler            // local wipe (failed passwords limit)
426aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank            dpm.setMaximumFailedPasswordsForWipe(mAdminName, aggregatePolicy.mPasswordMaxFails);
4271ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            // password expiration (days until a password expires).  API takes mSec.
4281ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            dpm.setPasswordExpirationTimeout(mAdminName,
429aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                    aggregatePolicy.getDPManagerPasswordExpirationTimeout());
4309b4988de43dbee6c06066caab63806e8c8303d7dMarc Blank            // password history length (number of previous passwords that may not be reused)
431aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank            dpm.setPasswordHistoryLength(mAdminName, aggregatePolicy.mPasswordHistory);
43222759bacd95385d95d3d9321f490763df1aba89dAndy Stadler            // password minimum complex characters.
43322759bacd95385d95d3d9321f490763df1aba89dAndy Stadler            // Note, in Exchange, "complex chars" simply means "non alpha", but in the DPM,
43422759bacd95385d95d3d9321f490763df1aba89dAndy Stadler            // setting the quality to complex also defaults min symbols=1 and min numeric=1.
43522759bacd95385d95d3d9321f490763df1aba89dAndy Stadler            // We always / safely clear minSymbols & minNumeric to zero (there is no policy
43622759bacd95385d95d3d9321f490763df1aba89dAndy Stadler            // configuration in which we explicitly require a minimum number of digits or symbols.)
43722759bacd95385d95d3d9321f490763df1aba89dAndy Stadler            dpm.setPasswordMinimumSymbols(mAdminName, 0);
43822759bacd95385d95d3d9321f490763df1aba89dAndy Stadler            dpm.setPasswordMinimumNumeric(mAdminName, 0);
439aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank            dpm.setPasswordMinimumNonLetter(mAdminName, aggregatePolicy.mPasswordComplexChars);
440d09cff08882e553afce919865a2cc60b657d4659Ben Komalo            // Device capabilities
441ce6916b32a98a568ceafb734d050801f4459a532Martin Hibdon            try {
442ce6916b32a98a568ceafb734d050801f4459a532Martin Hibdon                // If we are running in a managed policy, it is a securityException to even
443ce6916b32a98a568ceafb734d050801f4459a532Martin Hibdon                // call setCameraDisabled(), if is disabled is false. We have to swallow
444ce6916b32a98a568ceafb734d050801f4459a532Martin Hibdon                // the exception here.
445ce6916b32a98a568ceafb734d050801f4459a532Martin Hibdon                dpm.setCameraDisabled(mAdminName, aggregatePolicy.mDontAllowCamera);
446ce6916b32a98a568ceafb734d050801f4459a532Martin Hibdon            } catch (SecurityException e) {
447ce6916b32a98a568ceafb734d050801f4459a532Martin Hibdon                LogUtils.d(TAG, "SecurityException in setCameraDisabled, nothing changed");
448ce6916b32a98a568ceafb734d050801f4459a532Martin Hibdon            }
449d09cff08882e553afce919865a2cc60b657d4659Ben Komalo
450469f2987dc11d153434e50eb04dd6b83b924d09dAndy Stadler            // encryption required
451aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank            dpm.setStorageEncryption(mAdminName, aggregatePolicy.mRequireEncryption);
4523d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler        }
4533d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler    }
4543d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler
4553d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler    /**
4569ba506c4dd498150555f6c59aa758f7467bf9236Marc Blank     * Convenience method; see javadoc below
4579ba506c4dd498150555f6c59aa758f7467bf9236Marc Blank     */
4589ba506c4dd498150555f6c59aa758f7467bf9236Marc Blank    public static void setAccountHoldFlag(Context context, long accountId, boolean newState) {
4599ba506c4dd498150555f6c59aa758f7467bf9236Marc Blank        Account account = Account.restoreAccountWithId(context, accountId);
4609ba506c4dd498150555f6c59aa758f7467bf9236Marc Blank        if (account != null) {
4619ba506c4dd498150555f6c59aa758f7467bf9236Marc Blank            setAccountHoldFlag(context, account, newState);
462bc53491b95e4de2e30b04e696f30d209539ec87aMarc Blank            if (newState) {
463bc53491b95e4de2e30b04e696f30d209539ec87aMarc Blank                // Make sure there's a notification up
464bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook                final NotificationController nc =
465bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook                        NotificationControllerCreatorHolder.getInstance(context);
466bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook                nc.showSecurityNeededNotification(account);
467bc53491b95e4de2e30b04e696f30d209539ec87aMarc Blank            }
4689ba506c4dd498150555f6c59aa758f7467bf9236Marc Blank        }
4699ba506c4dd498150555f6c59aa758f7467bf9236Marc Blank    }
4709ba506c4dd498150555f6c59aa758f7467bf9236Marc Blank
4719ba506c4dd498150555f6c59aa758f7467bf9236Marc Blank    /**
4722a5eeea9213005060256054ec773e72406415ce4Andrew Stadler     * API: Set/Clear the "hold" flag in any account.  This flag serves a dual purpose:
4732a5eeea9213005060256054ec773e72406415ce4Andrew Stadler     * Setting it gives us an indication that it was blocked, and clearing it gives EAS a
4742a5eeea9213005060256054ec773e72406415ce4Andrew Stadler     * signal to try syncing again.
475076ab8307439f31e8197579f11b9bc3342fd5626Tony Mantler     * @param context context
4769ba506c4dd498150555f6c59aa758f7467bf9236Marc Blank     * @param account the account whose hold flag is to be set/cleared
4771ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * @param newState true = security hold, false = free to sync
4782a5eeea9213005060256054ec773e72406415ce4Andrew Stadler     */
4791ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler    public static void setAccountHoldFlag(Context context, Account account, boolean newState) {
4802a5eeea9213005060256054ec773e72406415ce4Andrew Stadler        if (newState) {
4812a5eeea9213005060256054ec773e72406415ce4Andrew Stadler            account.mFlags |= Account.FLAGS_SECURITY_HOLD;
4822a5eeea9213005060256054ec773e72406415ce4Andrew Stadler        } else {
4832a5eeea9213005060256054ec773e72406415ce4Andrew Stadler            account.mFlags &= ~Account.FLAGS_SECURITY_HOLD;
4842a5eeea9213005060256054ec773e72406415ce4Andrew Stadler        }
4852a5eeea9213005060256054ec773e72406415ce4Andrew Stadler        ContentValues cv = new ContentValues();
4862a5eeea9213005060256054ec773e72406415ce4Andrew Stadler        cv.put(AccountColumns.FLAGS, account.mFlags);
4871ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        account.update(context, cv);
4882a5eeea9213005060256054ec773e72406415ce4Andrew Stadler    }
4892a5eeea9213005060256054ec773e72406415ce4Andrew Stadler
4902a5eeea9213005060256054ec773e72406415ce4Andrew Stadler    /**
4913d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     * API: Sync service should call this any time a sync fails due to isActive() returning false.
492d62860821c2dbc14ab493b888cb129bd5addd53dAndrew Stadler     * This will kick off the notify-acquire-admin-state process and/or increase the security level.
493345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     * The caller needs to write the required policies into this account before making this call.
4943d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     * Should not be called from UI thread - uses DB lookups to prepare new notifications
495345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     *
496345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     * @param accountId the account for which sync cannot proceed
497345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     */
498345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    public void policiesRequired(long accountId) {
499f5418f1f93b02e7fab9f15eb201800b65510998eMarc Blank        Account account = Account.restoreAccountWithId(mContext, accountId);
500844b14f851ce748b7b204125264bb3343c1a9039Marc Blank        // In case the account has been deleted, just return
501844b14f851ce748b7b204125264bb3343c1a9039Marc Blank        if (account == null) return;
502f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        if (account.mPolicyKey == 0) return;
503f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        Policy policy = Policy.restorePolicyWithId(mContext, account.mPolicyKey);
504f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        if (policy == null) return;
50551c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon        if (DebugUtils.DEBUG) {
506560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy            LogUtils.d(TAG, "policiesRequired for " + account.mDisplayName + ": " + policy);
507aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank        }
5081ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler
5092a5eeea9213005060256054ec773e72406415ce4Andrew Stadler        // Mark the account as "on hold".
5101ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        setAccountHoldFlag(mContext, account, true);
5111ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler
512f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        // Put up an appropriate notification
513bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook        final NotificationController nc =
514bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook                NotificationControllerCreatorHolder.getInstance(mContext);
515f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        if (policy.mProtocolPoliciesUnsupported == null) {
516bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook            nc.showSecurityNeededNotification(account);
517f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        } else {
518bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook            nc.showSecurityUnsupportedNotification(account);
519f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        }
520f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    }
521f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank
522f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    public static void clearAccountPolicy(Context context, Account account) {
523f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        setAccountPolicy(context, account, null, null);
524f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    }
525f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank
526f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    /**
527f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     * Set the policy for an account atomically; this also removes any other policy associated with
528f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     * the account and sets the policy key for the account.  If policy is null, the policyKey is
529f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     * set to 0 and the securitySyncKey to null.  Also, update the account object to reflect the
530f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     * current policyKey and securitySyncKey
531f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     * @param context the caller's context
532f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     * @param account the account whose policy is to be set
533f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     * @param policy the policy to set, or null if we're clearing the policy
534f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     * @param securitySyncKey the security sync key for this account (ignored if policy is null)
535f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank     */
536f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    public static void setAccountPolicy(Context context, Account account, Policy policy,
537f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            String securitySyncKey) {
538f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
539f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank
540f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        // Make sure this is a valid policy set
541f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        if (policy != null) {
542f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            policy.normalize();
543f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            // Add the new policy (no account will yet reference this)
544f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            ops.add(ContentProviderOperation.newInsert(
545f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                    Policy.CONTENT_URI).withValues(policy.toContentValues()).build());
546f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            // Make the policyKey of the account our newly created policy, and set the sync key
547f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            ops.add(ContentProviderOperation.newUpdate(
548f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                    ContentUris.withAppendedId(Account.CONTENT_URI, account.mId))
549f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                    .withValueBackReference(AccountColumns.POLICY_KEY, 0)
550f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                    .withValue(AccountColumns.SECURITY_SYNC_KEY, securitySyncKey)
551f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                    .build());
552f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        } else {
553f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            ops.add(ContentProviderOperation.newUpdate(
554f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                    ContentUris.withAppendedId(Account.CONTENT_URI, account.mId))
555f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                    .withValue(AccountColumns.SECURITY_SYNC_KEY, null)
556f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                    .withValue(AccountColumns.POLICY_KEY, 0)
557f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                    .build());
558f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        }
559f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank
560f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        // Delete the previous policy associated with this account, if any
561f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        if (account.mPolicyKey > 0) {
562f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            ops.add(ContentProviderOperation.newDelete(
563f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                    ContentUris.withAppendedId(
564f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                            Policy.CONTENT_URI, account.mPolicyKey)).build());
565f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        }
566f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank
567f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        try {
568f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            context.getContentResolver().applyBatch(EmailContent.AUTHORITY, ops);
569f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            account.refresh(context);
570bd1e0b79e470afcfb9e6281711ad1038050c38fbYu Ping Hu            syncAccount(context, account);
571f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        } catch (RemoteException e) {
572f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank           // This is fatal to a remote process
573f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            throw new IllegalStateException("Exception setting account policy.");
574f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        } catch (OperationApplicationException e) {
575f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            // Can't happen; our provider doesn't throw this exception
576f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        }
577f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    }
578f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank
579bd1e0b79e470afcfb9e6281711ad1038050c38fbYu Ping Hu    private static void syncAccount(final Context context, final Account account) {
580bd1e0b79e470afcfb9e6281711ad1038050c38fbYu Ping Hu        final EmailServiceUtils.EmailServiceInfo info =
581bd1e0b79e470afcfb9e6281711ad1038050c38fbYu Ping Hu                EmailServiceUtils.getServiceInfo(context, account.getProtocol(context));
582bd1e0b79e470afcfb9e6281711ad1038050c38fbYu Ping Hu        final android.accounts.Account amAccount =
583bd1e0b79e470afcfb9e6281711ad1038050c38fbYu Ping Hu                new android.accounts.Account(account.mEmailAddress, info.accountType);
584bd1e0b79e470afcfb9e6281711ad1038050c38fbYu Ping Hu        final Bundle extras = new Bundle(3);
585bd1e0b79e470afcfb9e6281711ad1038050c38fbYu Ping Hu        extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
586bd1e0b79e470afcfb9e6281711ad1038050c38fbYu Ping Hu        extras.putBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, true);
587bd1e0b79e470afcfb9e6281711ad1038050c38fbYu Ping Hu        extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
588bd1e0b79e470afcfb9e6281711ad1038050c38fbYu Ping Hu        ContentResolver.requestSync(amAccount, EmailContent.AUTHORITY, extras);
589921c04d2ac5df091fb3c5cfa823e6dc2fc6cdf5aMartin Hibdon        LogUtils.i(TAG, "requestSync SecurityPolicy syncAccount %s, %s", account.toString(),
590921c04d2ac5df091fb3c5cfa823e6dc2fc6cdf5aMartin Hibdon                extras.toString());
591bd1e0b79e470afcfb9e6281711ad1038050c38fbYu Ping Hu    }
592bd1e0b79e470afcfb9e6281711ad1038050c38fbYu Ping Hu
593bd1e0b79e470afcfb9e6281711ad1038050c38fbYu Ping Hu    public void syncAccount(final Account account) {
594bd1e0b79e470afcfb9e6281711ad1038050c38fbYu Ping Hu        syncAccount(mContext, account);
595bd1e0b79e470afcfb9e6281711ad1038050c38fbYu Ping Hu    }
596bd1e0b79e470afcfb9e6281711ad1038050c38fbYu Ping Hu
59734662f11b2ff38f05219415f3672a52a7af2338fTony Mantler    public void setAccountPolicy(long accountId, Policy policy, String securityKey,
59834662f11b2ff38f05219415f3672a52a7af2338fTony Mantler            boolean notify) {
599f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        Account account = Account.restoreAccountWithId(mContext, accountId);
600f3440d33a9da544b202c05d1d5e4849674bcf5bfJay Shrauner        // In case the account has been deleted, just return
601f3440d33a9da544b202c05d1d5e4849674bcf5bfJay Shrauner        if (account == null) {
602f3440d33a9da544b202c05d1d5e4849674bcf5bfJay Shrauner            return;
603f3440d33a9da544b202c05d1d5e4849674bcf5bfJay Shrauner        }
604f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        Policy oldPolicy = null;
605f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        if (account.mPolicyKey > 0) {
606f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            oldPolicy = Policy.restorePolicyWithId(mContext, account.mPolicyKey);
607f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        }
608940a335bab634a849b45f62d711601d57f59aeb1Yu Ping Hu
609940a335bab634a849b45f62d711601d57f59aeb1Yu Ping Hu        // If attachment policies have changed, fix up any affected attachment records
610940a335bab634a849b45f62d711601d57f59aeb1Yu Ping Hu        if (oldPolicy != null && securityKey != null) {
611940a335bab634a849b45f62d711601d57f59aeb1Yu Ping Hu            if ((oldPolicy.mDontAllowAttachments != policy.mDontAllowAttachments) ||
612940a335bab634a849b45f62d711601d57f59aeb1Yu Ping Hu                    (oldPolicy.mMaxAttachmentSize != policy.mMaxAttachmentSize)) {
613940a335bab634a849b45f62d711601d57f59aeb1Yu Ping Hu                Policy.setAttachmentFlagsForNewPolicy(mContext, account, policy);
614940a335bab634a849b45f62d711601d57f59aeb1Yu Ping Hu            }
615940a335bab634a849b45f62d711601d57f59aeb1Yu Ping Hu        }
616940a335bab634a849b45f62d711601d57f59aeb1Yu Ping Hu
617f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        boolean policyChanged = (oldPolicy == null) || !oldPolicy.equals(policy);
618f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        if (!policyChanged && (TextUtilities.stringOrNullEquals(securityKey,
619f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                account.mSecuritySyncKey))) {
620560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy            LogUtils.d(Logging.LOG_TAG, "setAccountPolicy; policy unchanged");
621f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        } else {
622f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            setAccountPolicy(mContext, account, policy, securityKey);
623f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            policiesUpdated();
624f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        }
625f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank
626f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        boolean setHold = false;
627bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook        final NotificationController nc =
628bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook                NotificationControllerCreatorHolder.getInstance(mContext);
629f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        if (policy.mProtocolPoliciesUnsupported != null) {
630f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            // We can't support this, reasons in unsupportedRemotePolicies
631560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy            LogUtils.d(Logging.LOG_TAG,
632f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                    "Notify policies for " + account.mDisplayName + " not supported.");
633f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            setHold = true;
63434662f11b2ff38f05219415f3672a52a7af2338fTony Mantler            if (notify) {
635bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook                nc.showSecurityUnsupportedNotification(account);
63634662f11b2ff38f05219415f3672a52a7af2338fTony Mantler            }
637f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            // Erase data
638f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            Uri uri = EmailProvider.uiUri("uiaccountdata", accountId);
639f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            mContext.getContentResolver().delete(uri, null, null);
640f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        } else if (isActive(policy)) {
641f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            if (policyChanged) {
642560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy                LogUtils.d(Logging.LOG_TAG, "Notify policies for " + account.mDisplayName
643560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy                        + " changed.");
64434662f11b2ff38f05219415f3672a52a7af2338fTony Mantler                if (notify) {
64534662f11b2ff38f05219415f3672a52a7af2338fTony Mantler                    // Notify that policies changed
646bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook                    nc.showSecurityChangedNotification(account);
64734662f11b2ff38f05219415f3672a52a7af2338fTony Mantler                }
648bc53491b95e4de2e30b04e696f30d209539ec87aMarc Blank            } else {
649560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy                LogUtils.d(Logging.LOG_TAG, "Policy is active and unchanged; do not notify.");
650f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            }
651f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        } else {
652f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            setHold = true;
653560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy            LogUtils.d(Logging.LOG_TAG, "Notify policies for " + account.mDisplayName +
654f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                    " are not being enforced.");
65534662f11b2ff38f05219415f3672a52a7af2338fTony Mantler            if (notify) {
65634662f11b2ff38f05219415f3672a52a7af2338fTony Mantler                // Put up a notification
657bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook                nc.showSecurityNeededNotification(account);
65834662f11b2ff38f05219415f3672a52a7af2338fTony Mantler            }
659f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        }
660f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        // Set/clear the account hold.
661f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        setAccountHoldFlag(mContext, account, setHold);
6623d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler    }
6633d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler
6643d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler    /**
6653d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     * Called from the notification's intent receiver to register that the notification can be
6663d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     * cleared now.
6673d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     */
668c6df1d605fb3e235df6d4a21ae00632c9d6e3cc2Marc Blank    public void clearNotification() {
669bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook        final NotificationController nc =
670bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook                NotificationControllerCreatorHolder.getInstance(mContext);
671bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook
672bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook        nc.cancelSecurityNeededNotification();
6733d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler    }
6743d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler
6753d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler    /**
67650d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler     * API: Remote wipe (from server).  This is final, there is no confirmation.  It will only
677c82c1caf0138dbcef044ee41d24791f2ebeeb88aMarc Blank     * return to the caller if there is an unexpected failure.  The wipe includes external storage.
6783d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     */
67950d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler    public void remoteWipe() {
6803d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler        DevicePolicyManager dpm = getDPM();
6813d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler        if (dpm.isAdminActive(mAdminName)) {
682c82c1caf0138dbcef044ee41d24791f2ebeeb88aMarc Blank            dpm.wipeData(DevicePolicyManager.WIPE_EXTERNAL_STORAGE);
68350d1610c43c70039e9a02b862ec43cd6ee3d7906Andrew Stadler        } else {
684560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy            LogUtils.d(Logging.LOG_TAG, "Could not remote wipe because not device admin.");
6853d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler        }
686345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    }
687345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    /**
6883d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     * If we are not the active device admin, try to become so.
6893d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     *
690e7f4d3ebfcf497c015ba65be7ecebea8926b995cAndy Stadler     * Also checks for any policies that we have added during the lifetime of this app.
691e7f4d3ebfcf497c015ba65be7ecebea8926b995cAndy Stadler     * This catches the case where the user granted an earlier (smaller) set of policies
692e7f4d3ebfcf497c015ba65be7ecebea8926b995cAndy Stadler     * but an app upgrade requires that new policies be granted.
693e7f4d3ebfcf497c015ba65be7ecebea8926b995cAndy Stadler     *
6943d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     * @return true if we are already active, false if we are not
695345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler     */
6963d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler    public boolean isActiveAdmin() {
6973d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler        DevicePolicyManager dpm = getDPM();
698c2e638351c19ab22ad9ab4cce2853414c34724c3Andy Stadler        return dpm.isAdminActive(mAdminName)
699c2e638351c19ab22ad9ab4cce2853414c34724c3Andy Stadler                && dpm.hasGrantedPolicy(mAdminName, DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD)
700aa0a3553974972561e089f2780fb7f6743b3303eBen Komalo                && dpm.hasGrantedPolicy(mAdminName, DeviceAdminInfo.USES_ENCRYPTED_STORAGE)
701aa0a3553974972561e089f2780fb7f6743b3303eBen Komalo                && dpm.hasGrantedPolicy(mAdminName, DeviceAdminInfo.USES_POLICY_DISABLE_CAMERA);
7023d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler    }
703345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler
7043d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler    /**
7053d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     * Report admin component name - for making calls into device policy manager
7063d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     */
7073d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler    public ComponentName getAdminComponent() {
7083d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler        return mAdminName;
7093d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler    }
7103d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler
7113d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler    /**
71202d59d21949a77c60859b615312f02e6d8003490Marc Blank     * Delete all accounts whose security flags aren't zero (i.e. they have security enabled).
71302d59d21949a77c60859b615312f02e6d8003490Marc Blank     * This method is synchronous, so it should normally be called within a worker thread (the
71402d59d21949a77c60859b615312f02e6d8003490Marc Blank     * exception being for unit tests)
71502d59d21949a77c60859b615312f02e6d8003490Marc Blank     *
71602d59d21949a77c60859b615312f02e6d8003490Marc Blank     * @param context the caller's context
71702d59d21949a77c60859b615312f02e6d8003490Marc Blank     */
71802d59d21949a77c60859b615312f02e6d8003490Marc Blank    /*package*/ void deleteSecuredAccounts(Context context) {
71902d59d21949a77c60859b615312f02e6d8003490Marc Blank        ContentResolver cr = context.getContentResolver();
72002d59d21949a77c60859b615312f02e6d8003490Marc Blank        // Find all accounts with security and delete them
72102d59d21949a77c60859b615312f02e6d8003490Marc Blank        Cursor c = cr.query(Account.CONTENT_URI, EmailContent.ID_PROJECTION,
722aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                Account.SECURITY_NONZERO_SELECTION, null, null);
72302d59d21949a77c60859b615312f02e6d8003490Marc Blank        try {
724560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy            LogUtils.w(TAG, "Email administration disabled; deleting " + c.getCount() +
72502d59d21949a77c60859b615312f02e6d8003490Marc Blank                    " secured account(s)");
72602d59d21949a77c60859b615312f02e6d8003490Marc Blank            while (c.moveToNext()) {
727f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                long accountId = c.getLong(EmailContent.ID_PROJECTION_COLUMN);
728a60550e0eb08e0239d1fcea261b37ba592a35ba4Yu Ping Hu                Uri uri = EmailProvider.uiUri("uiaccount", accountId);
729f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                cr.delete(uri, null, null);
73002d59d21949a77c60859b615312f02e6d8003490Marc Blank            }
73102d59d21949a77c60859b615312f02e6d8003490Marc Blank        } finally {
73202d59d21949a77c60859b615312f02e6d8003490Marc Blank            c.close();
73302d59d21949a77c60859b615312f02e6d8003490Marc Blank        }
734f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        policiesUpdated();
735a60550e0eb08e0239d1fcea261b37ba592a35ba4Yu Ping Hu        AccountReconciler.reconcileAccounts(context);
73602d59d21949a77c60859b615312f02e6d8003490Marc Blank    }
73702d59d21949a77c60859b615312f02e6d8003490Marc Blank
73802d59d21949a77c60859b615312f02e6d8003490Marc Blank    /**
73902d59d21949a77c60859b615312f02e6d8003490Marc Blank     * Internal handler for enabled->disabled transitions.  Deletes all secured accounts.
740a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler     * Must call from worker thread, not on UI thread.
7413d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     */
74202d59d21949a77c60859b615312f02e6d8003490Marc Blank    /*package*/ void onAdminEnabled(boolean isEnabled) {
743856e09d76ab62272e660fd4a08e25637f17319a0Andrew Stadler        if (!isEnabled) {
744a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler            deleteSecuredAccounts(mContext);
7453d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler        }
7463d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler    }
7473d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler
7483d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler    /**
7491ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * Handle password expiration - if any accounts appear to have triggered this, put up
7501ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * warnings, or even shut them down.
7511ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     *
7521ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * NOTE:  If there are multiple accounts with password expiration policies, the device
7531ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * password will be set to expire in the shortest required interval (most secure).  The logic
7541ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * in this method operates based on the aggregate setting - irrespective of which account caused
7551ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * the expiration.  In other words, all accounts (that require expiration) will run/stop
7561ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * based on the requirements of the account with the shortest interval.
7571ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     */
758a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler    private void onPasswordExpiring(Context context) {
7591ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        // 1.  Do we have any accounts that matter here?
7601ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        long nextExpiringAccountId = findShortestExpiration(context);
7611ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler
7621ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        // 2.  If not, exit immediately
7631ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        if (nextExpiringAccountId == -1) {
7641ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            return;
7651ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        }
7661ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler
7671ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        // 3.  If yes, are we warning or expired?
7681ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        long expirationDate = getDPM().getPasswordExpiration(mAdminName);
7691ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        long timeUntilExpiration = expirationDate - System.currentTimeMillis();
7701ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        boolean expired = timeUntilExpiration < 0;
771bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook        final NotificationController nc =
772bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook                NotificationControllerCreatorHolder.getInstance(context);
7731ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        if (!expired) {
7741ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            // 4.  If warning, simply put up a generic notification and report that it came from
7751ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            // the shortest-expiring account.
776bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook            nc.showPasswordExpiringNotificationSynchronous(nextExpiringAccountId);
7771ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        } else {
7781ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            // 5.  Actually expired - find all accounts that expire passwords, and wipe them
779f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            boolean wiped = wipeExpiredAccounts(context);
7801ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            if (wiped) {
781bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook                nc.showPasswordExpiredNotificationSynchronous(nextExpiringAccountId);
7821ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            }
7831ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        }
7841ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler    }
7851ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler
7861ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler    /**
7871ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * Find the account with the shortest expiration time.  This is always assumed to be
7881ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * the account that forces the password to be refreshed.
7891ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * @return -1 if no expirations, or accountId if one is found
7901ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     */
791aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank    @VisibleForTesting
792aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank    /*package*/ static long findShortestExpiration(Context context) {
793aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank        long policyId = Utility.getFirstRowLong(context, Policy.CONTENT_URI, Policy.ID_PROJECTION,
794aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                HAS_PASSWORD_EXPIRATION, null, PolicyColumns.PASSWORD_EXPIRATION_DAYS + " ASC",
795aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                EmailContent.ID_PROJECTION_COLUMN, -1L);
796aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank        if (policyId < 0) return -1L;
797aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank        return Policy.getAccountIdWithPolicyKey(context, policyId);
7981ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler    }
7991ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler
8001ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler    /**
8011ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * For all accounts that require password expiration, put them in security hold and wipe
8021ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * their data.
803076ab8307439f31e8197579f11b9bc3342fd5626Tony Mantler     * @param context context
8041ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     * @return true if one or more accounts were wiped
8051ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler     */
806aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank    @VisibleForTesting
807f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    /*package*/ static boolean wipeExpiredAccounts(Context context) {
8081ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        boolean result = false;
809aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank        Cursor c = context.getContentResolver().query(Policy.CONTENT_URI,
810aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                Policy.ID_PROJECTION, HAS_PASSWORD_EXPIRATION, null, null);
811f3440d33a9da544b202c05d1d5e4849674bcf5bfJay Shrauner        if (c == null) {
812f3440d33a9da544b202c05d1d5e4849674bcf5bfJay Shrauner            return false;
813f3440d33a9da544b202c05d1d5e4849674bcf5bfJay Shrauner        }
8141ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        try {
8151ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            while (c.moveToNext()) {
816aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                long policyId = c.getLong(Policy.ID_PROJECTION_COLUMN);
817aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                long accountId = Policy.getAccountIdWithPolicyKey(context, policyId);
818aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                if (accountId < 0) continue;
819aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                Account account = Account.restoreAccountWithId(context, accountId);
820aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                if (account != null) {
821aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                    // Mark the account as "on hold".
822aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                    setAccountHoldFlag(context, account, true);
823aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                    // Erase data
824f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                    Uri uri = EmailProvider.uiUri("uiaccountdata", accountId);
825f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                    context.getContentResolver().delete(uri, null, null);
826aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                    // Report one or more were found
827aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blank                    result = true;
8281ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler                }
8291ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            }
8301ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        } finally {
8311ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler            c.close();
8321ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        }
8331ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        return result;
8341ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler    }
8351ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler
8361ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler    /**
837a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler     * Callback from EmailBroadcastProcessorService.  This provides the workers for the
838a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler     * DeviceAdminReceiver calls.  These should perform the work directly and not use async
839a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler     * threads for completion.
840a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler     */
841a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler    public static void onDeviceAdminReceiverMessage(Context context, int message) {
842a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler        SecurityPolicy instance = SecurityPolicy.getInstance(context);
843a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler        switch (message) {
844a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler            case DEVICE_ADMIN_MESSAGE_ENABLED:
845a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler                instance.onAdminEnabled(true);
846a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler                break;
847a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler            case DEVICE_ADMIN_MESSAGE_DISABLED:
848a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler                instance.onAdminEnabled(false);
849a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler                break;
850a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler            case DEVICE_ADMIN_MESSAGE_PASSWORD_CHANGED:
851a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler                // TODO make a small helper for this
852a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler                // Clear security holds (if any)
853a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler                Account.clearSecurityHoldOnAllAccounts(context);
854a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler                // Cancel any active notifications (if any are posted)
855bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook                final NotificationController nc =
856bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook                        NotificationControllerCreatorHolder.getInstance(context);
857bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook
858bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook                nc.cancelPasswordExpirationNotifications();
859a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler                break;
860a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler            case DEVICE_ADMIN_MESSAGE_PASSWORD_EXPIRING:
861a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler                instance.onPasswordExpiring(instance.mContext);
862a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler                break;
863a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler        }
864a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler    }
865a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler
866a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler    /**
8673d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     * Device Policy administrator.  This is primarily a listener for device state changes.
8683d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     * Note:  This is instantiated by incoming messages.
869a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler     * Note:  This is actually a BroadcastReceiver and must remain within the guidelines required
870a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler     *        for proper behavior, including avoidance of ANRs.
8715893e9e008d46cf4be0c7f709a6e77e2652c3dddAndrew Stadler     * Note:  We do not implement onPasswordFailed() because the default behavior of the
8725893e9e008d46cf4be0c7f709a6e77e2652c3dddAndrew Stadler     *        DevicePolicyManager - complete local wipe after 'n' failures - is sufficient.
8733d2b3b3b3554be2ac23d9a49fee00faa9693e857Andrew Stadler     */
8744ae83c58b3e136b4b1e859ee304ad1b332e9597fDianne Hackborn    public static class PolicyAdmin extends DeviceAdminReceiver {
875345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler
876345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        /**
877345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler         * Called after the administrator is first enabled.
878345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler         */
879345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        @Override
880345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        public void onEnabled(Context context, Intent intent) {
881a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler            EmailBroadcastProcessorService.processDevicePolicyMessage(context,
882a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler                    DEVICE_ADMIN_MESSAGE_ENABLED);
883345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        }
884856e09d76ab62272e660fd4a08e25637f17319a0Andrew Stadler
885345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        /**
886345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler         * Called prior to the administrator being disabled.
887345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler         */
888345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        @Override
889345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        public void onDisabled(Context context, Intent intent) {
890a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler            EmailBroadcastProcessorService.processDevicePolicyMessage(context,
891a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler                    DEVICE_ADMIN_MESSAGE_DISABLED);
892345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        }
893856e09d76ab62272e660fd4a08e25637f17319a0Andrew Stadler
894345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        /**
89502d59d21949a77c60859b615312f02e6d8003490Marc Blank         * Called when the user asks to disable administration; we return a warning string that
89602d59d21949a77c60859b615312f02e6d8003490Marc Blank         * will be presented to the user
89702d59d21949a77c60859b615312f02e6d8003490Marc Blank         */
89802d59d21949a77c60859b615312f02e6d8003490Marc Blank        @Override
89902d59d21949a77c60859b615312f02e6d8003490Marc Blank        public CharSequence onDisableRequested(Context context, Intent intent) {
90002d59d21949a77c60859b615312f02e6d8003490Marc Blank            return context.getString(R.string.disable_admin_warning);
90102d59d21949a77c60859b615312f02e6d8003490Marc Blank        }
90202d59d21949a77c60859b615312f02e6d8003490Marc Blank
90302d59d21949a77c60859b615312f02e6d8003490Marc Blank        /**
904345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler         * Called after the user has changed their password.
905345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler         */
906345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        @Override
907345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        public void onPasswordChanged(Context context, Intent intent) {
908a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler            EmailBroadcastProcessorService.processDevicePolicyMessage(context,
909a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler                    DEVICE_ADMIN_MESSAGE_PASSWORD_CHANGED);
9101ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        }
9111ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler
9121ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        /**
9131ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler         * Called when device password is expiring
9141ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler         */
9151ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        @Override
9161ca111c19c83d54ad23bd8615d9c648e09ec3366Andy Stadler        public void onPasswordExpiring(Context context, Intent intent) {
917a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler            EmailBroadcastProcessorService.processDevicePolicyMessage(context,
918a2269e84c6134bfd3506e5489c7ccfd60c32d41fAndy Stadler                    DEVICE_ADMIN_MESSAGE_PASSWORD_EXPIRING);
919345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler        }
920345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler    }
921345fb8b737c1632fb2a7e69ac44b8612be6237edAndrew Stadler}
922