blob: 71793a028013a3463ed2e2189e65f355990e5cb8 [file] [log] [blame]
Andrew Stadler345fb8b2010-01-26 17:24:15 -08001/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.email;
18
Andy Stadlere7f4d3e2010-12-08 16:06:16 -080019import android.app.admin.DeviceAdminInfo;
Dianne Hackborn6d001622010-02-26 17:26:45 -080020import android.app.admin.DeviceAdminReceiver;
21import android.app.admin.DevicePolicyManager;
Andrew Stadlerd6286082010-02-01 16:48:16 -080022import android.content.ComponentName;
Marc Blank2736c1a2011-10-20 10:13:02 -070023import android.content.ContentProviderOperation;
Marc Blank02d59d22010-10-25 11:49:29 -070024import android.content.ContentResolver;
Marc Blank2736c1a2011-10-20 10:13:02 -070025import android.content.ContentUris;
Andrew Stadler3d2b3b32010-02-05 11:10:39 -080026import android.content.ContentValues;
Andrew Stadler345fb8b2010-01-26 17:24:15 -080027import android.content.Context;
28import android.content.Intent;
Marc Blank2736c1a2011-10-20 10:13:02 -070029import android.content.OperationApplicationException;
Andrew Stadler345fb8b2010-01-26 17:24:15 -080030import android.database.Cursor;
Marc Blank2736c1a2011-10-20 10:13:02 -070031import android.os.RemoteException;
Andrew Stadler50d16102010-02-09 11:01:01 -080032import android.util.Log;
Andrew Stadler345fb8b2010-01-26 17:24:15 -080033
Marc Blankc6df1d62011-07-19 14:09:11 -070034import com.android.email.service.EmailBroadcastProcessorService;
35import com.android.emailcommon.Logging;
36import com.android.emailcommon.provider.Account;
37import com.android.emailcommon.provider.EmailContent;
38import com.android.emailcommon.provider.EmailContent.AccountColumns;
39import com.android.emailcommon.provider.EmailContent.PolicyColumns;
40import com.android.emailcommon.provider.Policy;
Marc Blank2736c1a2011-10-20 10:13:02 -070041import com.android.emailcommon.utility.TextUtilities;
Marc Blankc6df1d62011-07-19 14:09:11 -070042import com.android.emailcommon.utility.Utility;
43import com.google.common.annotations.VisibleForTesting;
44
Marc Blank2736c1a2011-10-20 10:13:02 -070045import java.util.ArrayList;
46
Andrew Stadler345fb8b2010-01-26 17:24:15 -080047/**
Andrew Stadlerd71d0b22010-02-09 17:24:55 -080048 * Utility functions to support reading and writing security policies, and handshaking the device
49 * into and out of various security states.
Andrew Stadler345fb8b2010-01-26 17:24:15 -080050 */
51public class SecurityPolicy {
Marc Blankaeee10e2011-04-27 17:12:06 -070052 private static final String TAG = "Email/SecurityPolicy";
Andrew Stadler345fb8b2010-01-26 17:24:15 -080053 private static SecurityPolicy sInstance = null;
54 private Context mContext;
Andrew Stadlerd6286082010-02-01 16:48:16 -080055 private DevicePolicyManager mDPM;
Ben Komalod09cff02011-05-06 14:57:47 -070056 private final ComponentName mAdminName;
Marc Blankaeee10e2011-04-27 17:12:06 -070057 private Policy mAggregatePolicy;
Andrew Stadler2a5eeea2010-02-08 17:42:42 -080058
Andy Stadlera2269e82010-12-30 00:16:55 -080059 // Messages used for DevicePolicyManager callbacks
60 private static final int DEVICE_ADMIN_MESSAGE_ENABLED = 1;
61 private static final int DEVICE_ADMIN_MESSAGE_DISABLED = 2;
62 private static final int DEVICE_ADMIN_MESSAGE_PASSWORD_CHANGED = 3;
63 private static final int DEVICE_ADMIN_MESSAGE_PASSWORD_EXPIRING = 4;
64
Marc Blankaeee10e2011-04-27 17:12:06 -070065 private static final String HAS_PASSWORD_EXPIRATION =
66 PolicyColumns.PASSWORD_EXPIRATION_DAYS + ">0";
67
Andrew Stadler345fb8b2010-01-26 17:24:15 -080068 /**
69 * Get the security policy instance
70 */
71 public synchronized static SecurityPolicy getInstance(Context context) {
72 if (sInstance == null) {
Andy Stadlera2269e82010-12-30 00:16:55 -080073 sInstance = new SecurityPolicy(context.getApplicationContext());
Andrew Stadler345fb8b2010-01-26 17:24:15 -080074 }
75 return sInstance;
76 }
77
78 /**
79 * Private constructor (one time only)
80 */
81 private SecurityPolicy(Context context) {
Makoto Onuki968be442010-05-20 16:11:26 -070082 mContext = context.getApplicationContext();
Andrew Stadlerd6286082010-02-01 16:48:16 -080083 mDPM = null;
84 mAdminName = new ComponentName(context, PolicyAdmin.class);
85 mAggregatePolicy = null;
Andrew Stadler345fb8b2010-01-26 17:24:15 -080086 }
87
88 /**
89 * For testing only: Inject context into already-created instance
90 */
91 /* package */ void setContext(Context context) {
92 mContext = context;
93 }
94
95 /**
96 * Compute the aggregate policy for all accounts that require it, and record it.
97 *
98 * The business logic is as follows:
99 * min password length take the max
100 * password mode take the max (strongest mode)
101 * max password fails take the min
102 * max screen lock time take the min
103 * require remote wipe take the max (logical or)
Marc Blank9b4988d2010-06-09 16:18:57 -0700104 * password history take the max (strongest mode)
Andy Stadler1ca111c2010-12-01 12:58:36 -0800105 * password expiration take the min (strongest mode)
Marc Blank9b4988d2010-06-09 16:18:57 -0700106 * password complex chars take the max (strongest mode)
Andy Stadler469f2982011-01-13 13:12:55 -0800107 * encryption take the max (logical or)
Makoto Onuki968be442010-05-20 16:11:26 -0700108 *
Andrew Stadlerd6286082010-02-01 16:48:16 -0800109 * @return a policy representing the strongest aggregate. If no policy sets are defined,
110 * a lightweight "nothing required" policy will be returned. Never null.
Andrew Stadler345fb8b2010-01-26 17:24:15 -0800111 */
Ben Komalod09cff02011-05-06 14:57:47 -0700112 @VisibleForTesting
113 Policy computeAggregatePolicy() {
Andrew Stadler345fb8b2010-01-26 17:24:15 -0800114 boolean policiesFound = false;
Marc Blankaeee10e2011-04-27 17:12:06 -0700115 Policy aggregate = new Policy();
116 aggregate.mPasswordMinLength = Integer.MIN_VALUE;
117 aggregate.mPasswordMode = Integer.MIN_VALUE;
118 aggregate.mPasswordMaxFails = Integer.MAX_VALUE;
119 aggregate.mPasswordHistory = Integer.MIN_VALUE;
120 aggregate.mPasswordExpirationDays = Integer.MAX_VALUE;
121 aggregate.mPasswordComplexChars = Integer.MIN_VALUE;
122 aggregate.mMaxScreenLockTime = Integer.MAX_VALUE;
123 aggregate.mRequireRemoteWipe = false;
124 aggregate.mRequireEncryption = false;
Ben Komaloe76962b2011-07-01 12:34:03 -0700125
126 // This can never be supported at this time. It exists only for historic reasons where
127 // this was able to be supported prior to the introduction of proper removable storage
128 // support for external storage.
Marc Blankaeee10e2011-04-27 17:12:06 -0700129 aggregate.mRequireEncryptionExternal = false;
Andrew Stadler345fb8b2010-01-26 17:24:15 -0800130
Marc Blankaeee10e2011-04-27 17:12:06 -0700131 Cursor c = mContext.getContentResolver().query(Policy.CONTENT_URI,
132 Policy.CONTENT_PROJECTION, null, null, null);
133 Policy policy = new Policy();
Andrew Stadler345fb8b2010-01-26 17:24:15 -0800134 try {
135 while (c.moveToNext()) {
Marc Blankaeee10e2011-04-27 17:12:06 -0700136 policy.restore(c);
137 if (Email.DEBUG) {
138 Log.d(TAG, "Aggregate from: " + policy);
Andrew Stadler345fb8b2010-01-26 17:24:15 -0800139 }
Marc Blankaeee10e2011-04-27 17:12:06 -0700140 aggregate.mPasswordMinLength =
141 Math.max(policy.mPasswordMinLength, aggregate.mPasswordMinLength);
142 aggregate.mPasswordMode = Math.max(policy.mPasswordMode, aggregate.mPasswordMode);
143 if (policy.mPasswordMaxFails > 0) {
144 aggregate.mPasswordMaxFails =
145 Math.min(policy.mPasswordMaxFails, aggregate.mPasswordMaxFails);
146 }
147 if (policy.mMaxScreenLockTime > 0) {
148 aggregate.mMaxScreenLockTime = Math.min(policy.mMaxScreenLockTime,
149 aggregate.mMaxScreenLockTime);
150 }
151 if (policy.mPasswordHistory > 0) {
152 aggregate.mPasswordHistory =
153 Math.max(policy.mPasswordHistory, aggregate.mPasswordHistory);
154 }
155 if (policy.mPasswordExpirationDays > 0) {
156 aggregate.mPasswordExpirationDays =
157 Math.min(policy.mPasswordExpirationDays, aggregate.mPasswordExpirationDays);
158 }
159 if (policy.mPasswordComplexChars > 0) {
160 aggregate.mPasswordComplexChars = Math.max(policy.mPasswordComplexChars,
161 aggregate.mPasswordComplexChars);
162 }
163 aggregate.mRequireRemoteWipe |= policy.mRequireRemoteWipe;
164 aggregate.mRequireEncryption |= policy.mRequireEncryption;
Ben Komalod09cff02011-05-06 14:57:47 -0700165 aggregate.mDontAllowCamera |= policy.mDontAllowCamera;
Marc Blankaeee10e2011-04-27 17:12:06 -0700166 policiesFound = true;
Andrew Stadler345fb8b2010-01-26 17:24:15 -0800167 }
168 } finally {
169 c.close();
170 }
171 if (policiesFound) {
Andrew Stadler3d2b3b32010-02-05 11:10:39 -0800172 // final cleanup pass converts any untouched min/max values to zero (not specified)
Marc Blankaeee10e2011-04-27 17:12:06 -0700173 if (aggregate.mPasswordMinLength == Integer.MIN_VALUE) aggregate.mPasswordMinLength = 0;
174 if (aggregate.mPasswordMode == Integer.MIN_VALUE) aggregate.mPasswordMode = 0;
175 if (aggregate.mPasswordMaxFails == Integer.MAX_VALUE) aggregate.mPasswordMaxFails = 0;
176 if (aggregate.mMaxScreenLockTime == Integer.MAX_VALUE) aggregate.mMaxScreenLockTime = 0;
177 if (aggregate.mPasswordHistory == Integer.MIN_VALUE) aggregate.mPasswordHistory = 0;
178 if (aggregate.mPasswordExpirationDays == Integer.MAX_VALUE)
179 aggregate.mPasswordExpirationDays = 0;
180 if (aggregate.mPasswordComplexChars == Integer.MIN_VALUE)
181 aggregate.mPasswordComplexChars = 0;
182 if (Email.DEBUG) {
183 Log.d(TAG, "Calculated Aggregate: " + aggregate);
184 }
185 return aggregate;
Andrew Stadler345fb8b2010-01-26 17:24:15 -0800186 }
Marc Blankaeee10e2011-04-27 17:12:06 -0700187 if (Email.DEBUG) {
188 Log.d(TAG, "Calculated Aggregate: no policy");
189 }
190 return Policy.NO_POLICY;
Andrew Stadler345fb8b2010-01-26 17:24:15 -0800191 }
192
193 /**
Andrew Stadler50d16102010-02-09 11:01:01 -0800194 * Return updated aggregate policy, from cached value if possible
195 */
Marc Blankaeee10e2011-04-27 17:12:06 -0700196 public synchronized Policy getAggregatePolicy() {
Andrew Stadler50d16102010-02-09 11:01:01 -0800197 if (mAggregatePolicy == null) {
198 mAggregatePolicy = computeAggregatePolicy();
199 }
200 return mAggregatePolicy;
201 }
202
203 /**
Andrew Stadlerd6286082010-02-01 16:48:16 -0800204 * Get the dpm. This mainly allows us to make some utility calls without it, for testing.
205 */
Andy Stadlera0d08052011-01-19 11:40:48 -0800206 /* package */ synchronized DevicePolicyManager getDPM() {
Andrew Stadlerd6286082010-02-01 16:48:16 -0800207 if (mDPM == null) {
208 mDPM = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
209 }
210 return mDPM;
211 }
212
213 /**
Marc Blank2736c1a2011-10-20 10:13:02 -0700214 * API: Report that policies may have been updated due to rewriting values in an Account; we
215 * clear the aggregate policy (so it can be recomputed) and set the policies in the DPM
Andrew Stadlerd6286082010-02-01 16:48:16 -0800216 */
Marc Blank2736c1a2011-10-20 10:13:02 -0700217 public synchronized void policiesUpdated() {
Andrew Stadlerd6286082010-02-01 16:48:16 -0800218 mAggregatePolicy = null;
Marc Blank2736c1a2011-10-20 10:13:02 -0700219 setActivePolicies();
Andrew Stadlerd6286082010-02-01 16:48:16 -0800220 }
221
222 /**
Andrew Stadler50d16102010-02-09 11:01:01 -0800223 * API: Report that policies may have been updated *and* the caller vouches that the
224 * change is a reduction in policies. This forces an immediate change to device state.
225 * Typically used when deleting accounts, although we may use it for server-side policy
226 * rollbacks.
227 */
228 public void reducePolicies() {
Marc Blankaeee10e2011-04-27 17:12:06 -0700229 if (Email.DEBUG) {
230 Log.d(TAG, "reducePolicies");
231 }
Marc Blank2736c1a2011-10-20 10:13:02 -0700232 policiesUpdated();
Andy Stadlera0d08052011-01-19 11:40:48 -0800233 }
234
235 /**
Andy Stadler469f2982011-01-13 13:12:55 -0800236 * API: Query used to determine if a given policy is "active" (the device is operating at
237 * the required security level).
238 *
Ben Komalod09cff02011-05-06 14:57:47 -0700239 * @param policy the policies requested, or null to check aggregate stored policies
Andy Stadler469f2982011-01-13 13:12:55 -0800240 * @return true if the requested policies are active, false if not.
241 */
Marc Blankaeee10e2011-04-27 17:12:06 -0700242 public boolean isActive(Policy policy) {
243 int reasons = getInactiveReasons(policy);
244 if (Email.DEBUG && (reasons != 0)) {
245 StringBuilder sb = new StringBuilder("isActive for " + policy + ": ");
246 if (reasons == 0) {
247 sb.append("true");
248 } else {
249 sb.append("FALSE -> ");
250 }
251 if ((reasons & INACTIVE_NEED_ACTIVATION) != 0) {
252 sb.append("no_admin ");
253 }
254 if ((reasons & INACTIVE_NEED_CONFIGURATION) != 0) {
255 sb.append("config ");
256 }
257 if ((reasons & INACTIVE_NEED_PASSWORD) != 0) {
258 sb.append("password ");
259 }
260 if ((reasons & INACTIVE_NEED_ENCRYPTION) != 0) {
261 sb.append("encryption ");
262 }
Marc Blank2736c1a2011-10-20 10:13:02 -0700263 if ((reasons & INACTIVE_PROTOCOL_POLICIES) != 0) {
264 sb.append("protocol ");
265 }
Marc Blankaeee10e2011-04-27 17:12:06 -0700266 Log.d(TAG, sb.toString());
267 }
Andy Stadler469f2982011-01-13 13:12:55 -0800268 return reasons == 0;
269 }
270
271 /**
272 * Return bits from isActive: Device Policy Manager has not been activated
273 */
274 public final static int INACTIVE_NEED_ACTIVATION = 1;
275
276 /**
277 * Return bits from isActive: Some required configuration is not correct (no user action).
278 */
279 public final static int INACTIVE_NEED_CONFIGURATION = 2;
280
281 /**
282 * Return bits from isActive: Password needs to be set or updated
283 */
284 public final static int INACTIVE_NEED_PASSWORD = 4;
285
286 /**
287 * Return bits from isActive: Encryption has not be enabled
288 */
289 public final static int INACTIVE_NEED_ENCRYPTION = 8;
290
291 /**
Marc Blank2736c1a2011-10-20 10:13:02 -0700292 * Return bits from isActive: Protocol-specific policies cannot be enforced
293 */
294 public final static int INACTIVE_PROTOCOL_POLICIES = 16;
295
296 /**
Andrew Stadlerd6286082010-02-01 16:48:16 -0800297 * API: Query used to determine if a given policy is "active" (the device is operating at
Andrew Stadler3d2b3b32010-02-05 11:10:39 -0800298 * the required security level).
Andrew Stadler345fb8b2010-01-26 17:24:15 -0800299 *
Andrew Stadler3d2b3b32010-02-05 11:10:39 -0800300 * This can be used when syncing a specific account, by passing a specific set of policies
301 * for that account. Or, it can be used at any time to compare the device
302 * state against the aggregate set of device policies stored in all accounts.
303 *
304 * This method is for queries only, and does not trigger any change in device state.
305 *
Andy Stadler1ca111c2010-12-01 12:58:36 -0800306 * NOTE: If there are multiple accounts with password expiration policies, the device
307 * password will be set to expire in the shortest required interval (most secure). This method
308 * will return 'false' as soon as the password expires - irrespective of which account caused
309 * the expiration. In other words, all accounts (that require expiration) will run/stop
310 * based on the requirements of the account with the shortest interval.
311 *
Ben Komalod09cff02011-05-06 14:57:47 -0700312 * @param policy the policies requested, or null to check aggregate stored policies
Andy Stadler469f2982011-01-13 13:12:55 -0800313 * @return zero if the requested policies are active, non-zero bits indicates that more work
314 * is needed (typically, by the user) before the required security polices are fully active.
Andrew Stadler345fb8b2010-01-26 17:24:15 -0800315 */
Marc Blankaeee10e2011-04-27 17:12:06 -0700316 public int getInactiveReasons(Policy policy) {
Andrew Stadler50d16102010-02-09 11:01:01 -0800317 // select aggregate set if needed
Marc Blankaeee10e2011-04-27 17:12:06 -0700318 if (policy == null) {
319 policy = getAggregatePolicy();
Andrew Stadler50d16102010-02-09 11:01:01 -0800320 }
321 // quick check for the "empty set" of no policies
Marc Blankaeee10e2011-04-27 17:12:06 -0700322 if (policy == Policy.NO_POLICY) {
Andy Stadler469f2982011-01-13 13:12:55 -0800323 return 0;
Andrew Stadler50d16102010-02-09 11:01:01 -0800324 }
Andy Stadler469f2982011-01-13 13:12:55 -0800325 int reasons = 0;
Andrew Stadlerd6286082010-02-01 16:48:16 -0800326 DevicePolicyManager dpm = getDPM();
Andy Stadlere7f4d3e2010-12-08 16:06:16 -0800327 if (isActiveAdmin()) {
Andrew Stadlerd6286082010-02-01 16:48:16 -0800328 // check each policy explicitly
Marc Blankaeee10e2011-04-27 17:12:06 -0700329 if (policy.mPasswordMinLength > 0) {
330 if (dpm.getPasswordMinimumLength(mAdminName) < policy.mPasswordMinLength) {
Andy Stadler469f2982011-01-13 13:12:55 -0800331 reasons |= INACTIVE_NEED_PASSWORD;
Andrew Stadlerd6286082010-02-01 16:48:16 -0800332 }
333 }
Marc Blankaeee10e2011-04-27 17:12:06 -0700334 if (policy.mPasswordMode > 0) {
335 if (dpm.getPasswordQuality(mAdminName) < policy.getDPManagerPasswordQuality()) {
Andy Stadler469f2982011-01-13 13:12:55 -0800336 reasons |= INACTIVE_NEED_PASSWORD;
Andrew Stadlerd6286082010-02-01 16:48:16 -0800337 }
338 if (!dpm.isActivePasswordSufficient()) {
Andy Stadler469f2982011-01-13 13:12:55 -0800339 reasons |= INACTIVE_NEED_PASSWORD;
Andrew Stadlerd6286082010-02-01 16:48:16 -0800340 }
341 }
Marc Blankaeee10e2011-04-27 17:12:06 -0700342 if (policy.mMaxScreenLockTime > 0) {
Andrew Stadlerd6286082010-02-01 16:48:16 -0800343 // Note, we use seconds, dpm uses milliseconds
Marc Blankaeee10e2011-04-27 17:12:06 -0700344 if (dpm.getMaximumTimeToLock(mAdminName) > policy.mMaxScreenLockTime * 1000) {
Andy Stadler469f2982011-01-13 13:12:55 -0800345 reasons |= INACTIVE_NEED_CONFIGURATION;
Andrew Stadlerd6286082010-02-01 16:48:16 -0800346 }
347 }
Marc Blankaeee10e2011-04-27 17:12:06 -0700348 if (policy.mPasswordExpirationDays > 0) {
Andy Stadler1ca111c2010-12-01 12:58:36 -0800349 // confirm that expirations are currently set
350 long currentTimeout = dpm.getPasswordExpirationTimeout(mAdminName);
351 if (currentTimeout == 0
Marc Blankaeee10e2011-04-27 17:12:06 -0700352 || currentTimeout > policy.getDPManagerPasswordExpirationTimeout()) {
Andy Stadler469f2982011-01-13 13:12:55 -0800353 reasons |= INACTIVE_NEED_PASSWORD;
Andy Stadler1ca111c2010-12-01 12:58:36 -0800354 }
355 // confirm that the current password hasn't expired
356 long expirationDate = dpm.getPasswordExpiration(mAdminName);
357 long timeUntilExpiration = expirationDate - System.currentTimeMillis();
358 boolean expired = timeUntilExpiration < 0;
359 if (expired) {
Andy Stadler469f2982011-01-13 13:12:55 -0800360 reasons |= INACTIVE_NEED_PASSWORD;
Andy Stadler1ca111c2010-12-01 12:58:36 -0800361 }
Marc Blank9b4988d2010-06-09 16:18:57 -0700362 }
Marc Blankaeee10e2011-04-27 17:12:06 -0700363 if (policy.mPasswordHistory > 0) {
364 if (dpm.getPasswordHistoryLength(mAdminName) < policy.mPasswordHistory) {
Marc Blanke86d8af2011-08-28 16:34:28 -0700365 // There's no user action for changes here; this is just a configuration change
366 reasons |= INACTIVE_NEED_CONFIGURATION;
Marc Blank9b4988d2010-06-09 16:18:57 -0700367 }
368 }
Marc Blankaeee10e2011-04-27 17:12:06 -0700369 if (policy.mPasswordComplexChars > 0) {
370 if (dpm.getPasswordMinimumNonLetter(mAdminName) < policy.mPasswordComplexChars) {
Andy Stadler469f2982011-01-13 13:12:55 -0800371 reasons |= INACTIVE_NEED_PASSWORD;
372 }
373 }
Marc Blankaeee10e2011-04-27 17:12:06 -0700374 if (policy.mRequireEncryption) {
Andy Stadlerc2e63832011-01-17 12:54:40 -0800375 int encryptionStatus = getDPM().getStorageEncryptionStatus();
Andy Stadler469f2982011-01-13 13:12:55 -0800376 if (encryptionStatus != DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE) {
377 reasons |= INACTIVE_NEED_ENCRYPTION;
Marc Blank9b4988d2010-06-09 16:18:57 -0700378 }
379 }
Marc Blankce582522011-08-21 21:06:54 -0700380 if (policy.mDontAllowCamera && !dpm.getCameraDisabled(mAdminName)) {
381 reasons |= INACTIVE_NEED_CONFIGURATION;
382 }
Andrew Stadlerd6286082010-02-01 16:48:16 -0800383 // password failures are counted locally - no test required here
384 // no check required for remote wipe (it's supported, if we're the admin)
Andrew Stadler2a5eeea2010-02-08 17:42:42 -0800385
Marc Blank2736c1a2011-10-20 10:13:02 -0700386 if (policy.mProtocolPoliciesUnsupported != null) {
387 reasons |= INACTIVE_PROTOCOL_POLICIES;
388 }
389
Andy Stadler469f2982011-01-13 13:12:55 -0800390 // If we made it all the way, reasons == 0 here. Otherwise it's a list of grievances.
391 return reasons;
Andrew Stadlerd6286082010-02-01 16:48:16 -0800392 }
Andrew Stadlerd71d0b22010-02-09 17:24:55 -0800393 // return false, not active
Andy Stadler469f2982011-01-13 13:12:55 -0800394 return INACTIVE_NEED_ACTIVATION;
Andrew Stadler345fb8b2010-01-26 17:24:15 -0800395 }
396
397 /**
Andrew Stadler50d16102010-02-09 11:01:01 -0800398 * Set the requested security level based on the aggregate set of requests.
399 * If the set is empty, we release our device administration. If the set is non-empty,
400 * we only proceed if we are already active as an admin.
Andrew Stadler3d2b3b32010-02-05 11:10:39 -0800401 */
402 public void setActivePolicies() {
403 DevicePolicyManager dpm = getDPM();
Andrew Stadler50d16102010-02-09 11:01:01 -0800404 // compute aggregate set of policies
Marc Blankaeee10e2011-04-27 17:12:06 -0700405 Policy aggregatePolicy = getAggregatePolicy();
Andrew Stadler50d16102010-02-09 11:01:01 -0800406 // if empty set, detach from policy manager
Marc Blankaeee10e2011-04-27 17:12:06 -0700407 if (aggregatePolicy == Policy.NO_POLICY) {
408 if (Email.DEBUG) {
409 Log.d(TAG, "setActivePolicies: none, remove admin");
410 }
Andrew Stadler50d16102010-02-09 11:01:01 -0800411 dpm.removeActiveAdmin(mAdminName);
Andy Stadlere7f4d3e2010-12-08 16:06:16 -0800412 } else if (isActiveAdmin()) {
Marc Blankaeee10e2011-04-27 17:12:06 -0700413 if (Email.DEBUG) {
414 Log.d(TAG, "setActivePolicies: " + aggregatePolicy);
415 }
Andrew Stadler3d2b3b32010-02-05 11:10:39 -0800416 // set each policy in the policy manager
417 // password mode & length
Marc Blankaeee10e2011-04-27 17:12:06 -0700418 dpm.setPasswordQuality(mAdminName, aggregatePolicy.getDPManagerPasswordQuality());
419 dpm.setPasswordMinimumLength(mAdminName, aggregatePolicy.mPasswordMinLength);
Andrew Stadler3d2b3b32010-02-05 11:10:39 -0800420 // screen lock time
Marc Blankaeee10e2011-04-27 17:12:06 -0700421 dpm.setMaximumTimeToLock(mAdminName, aggregatePolicy.mMaxScreenLockTime * 1000);
Andrew Stadler3d2b3b32010-02-05 11:10:39 -0800422 // local wipe (failed passwords limit)
Marc Blankaeee10e2011-04-27 17:12:06 -0700423 dpm.setMaximumFailedPasswordsForWipe(mAdminName, aggregatePolicy.mPasswordMaxFails);
Andy Stadler1ca111c2010-12-01 12:58:36 -0800424 // password expiration (days until a password expires). API takes mSec.
425 dpm.setPasswordExpirationTimeout(mAdminName,
Marc Blankaeee10e2011-04-27 17:12:06 -0700426 aggregatePolicy.getDPManagerPasswordExpirationTimeout());
Marc Blank9b4988d2010-06-09 16:18:57 -0700427 // password history length (number of previous passwords that may not be reused)
Marc Blankaeee10e2011-04-27 17:12:06 -0700428 dpm.setPasswordHistoryLength(mAdminName, aggregatePolicy.mPasswordHistory);
Andy Stadler22759ba2011-03-16 09:48:08 -0700429 // password minimum complex characters.
430 // Note, in Exchange, "complex chars" simply means "non alpha", but in the DPM,
431 // setting the quality to complex also defaults min symbols=1 and min numeric=1.
432 // We always / safely clear minSymbols & minNumeric to zero (there is no policy
433 // configuration in which we explicitly require a minimum number of digits or symbols.)
434 dpm.setPasswordMinimumSymbols(mAdminName, 0);
435 dpm.setPasswordMinimumNumeric(mAdminName, 0);
Marc Blankaeee10e2011-04-27 17:12:06 -0700436 dpm.setPasswordMinimumNonLetter(mAdminName, aggregatePolicy.mPasswordComplexChars);
Ben Komalod09cff02011-05-06 14:57:47 -0700437 // Device capabilities
438 dpm.setCameraDisabled(mAdminName, aggregatePolicy.mDontAllowCamera);
439
Andy Stadler469f2982011-01-13 13:12:55 -0800440 // encryption required
Marc Blankaeee10e2011-04-27 17:12:06 -0700441 dpm.setStorageEncryption(mAdminName, aggregatePolicy.mRequireEncryption);
Andrew Stadler3d2b3b32010-02-05 11:10:39 -0800442 }
443 }
444
445 /**
Marc Blank9ba506c2011-02-08 18:54:56 -0800446 * Convenience method; see javadoc below
447 */
448 public static void setAccountHoldFlag(Context context, long accountId, boolean newState) {
449 Account account = Account.restoreAccountWithId(context, accountId);
450 if (account != null) {
451 setAccountHoldFlag(context, account, newState);
452 }
453 }
454
455 /**
Andrew Stadler2a5eeea2010-02-08 17:42:42 -0800456 * API: Set/Clear the "hold" flag in any account. This flag serves a dual purpose:
457 * Setting it gives us an indication that it was blocked, and clearing it gives EAS a
458 * signal to try syncing again.
Andy Stadler1ca111c2010-12-01 12:58:36 -0800459 * @param context
Marc Blank9ba506c2011-02-08 18:54:56 -0800460 * @param account the account whose hold flag is to be set/cleared
Andy Stadler1ca111c2010-12-01 12:58:36 -0800461 * @param newState true = security hold, false = free to sync
Andrew Stadler2a5eeea2010-02-08 17:42:42 -0800462 */
Andy Stadler1ca111c2010-12-01 12:58:36 -0800463 public static void setAccountHoldFlag(Context context, Account account, boolean newState) {
Andrew Stadler2a5eeea2010-02-08 17:42:42 -0800464 if (newState) {
465 account.mFlags |= Account.FLAGS_SECURITY_HOLD;
466 } else {
467 account.mFlags &= ~Account.FLAGS_SECURITY_HOLD;
468 }
469 ContentValues cv = new ContentValues();
470 cv.put(AccountColumns.FLAGS, account.mFlags);
Andy Stadler1ca111c2010-12-01 12:58:36 -0800471 account.update(context, cv);
Andrew Stadler2a5eeea2010-02-08 17:42:42 -0800472 }
473
474 /**
Andrew Stadler3d2b3b32010-02-05 11:10:39 -0800475 * API: Sync service should call this any time a sync fails due to isActive() returning false.
Andrew Stadlerd6286082010-02-01 16:48:16 -0800476 * This will kick off the notify-acquire-admin-state process and/or increase the security level.
Andrew Stadler345fb8b2010-01-26 17:24:15 -0800477 * The caller needs to write the required policies into this account before making this call.
Andrew Stadler3d2b3b32010-02-05 11:10:39 -0800478 * Should not be called from UI thread - uses DB lookups to prepare new notifications
Andrew Stadler345fb8b2010-01-26 17:24:15 -0800479 *
480 * @param accountId the account for which sync cannot proceed
481 */
482 public void policiesRequired(long accountId) {
Marc Blankf5418f12011-06-13 15:32:27 -0700483 Account account = Account.restoreAccountWithId(mContext, accountId);
Marc Blank844b14f2011-01-26 18:18:45 -0800484 // In case the account has been deleted, just return
485 if (account == null) return;
Marc Blank2736c1a2011-10-20 10:13:02 -0700486 if (account.mPolicyKey == 0) return;
487 Policy policy = Policy.restorePolicyWithId(mContext, account.mPolicyKey);
488 if (policy == null) return;
Marc Blankaeee10e2011-04-27 17:12:06 -0700489 if (Email.DEBUG) {
Marc Blank2736c1a2011-10-20 10:13:02 -0700490 Log.d(TAG, "policiesRequired for " + account.mDisplayName + ": " + policy);
Marc Blankaeee10e2011-04-27 17:12:06 -0700491 }
Andy Stadler1ca111c2010-12-01 12:58:36 -0800492
Andrew Stadler2a5eeea2010-02-08 17:42:42 -0800493 // Mark the account as "on hold".
Andy Stadler1ca111c2010-12-01 12:58:36 -0800494 setAccountHoldFlag(mContext, account, true);
495
Marc Blank2736c1a2011-10-20 10:13:02 -0700496 // Put up an appropriate notification
497 if (policy.mProtocolPoliciesUnsupported == null) {
498 NotificationController.getInstance(mContext).showSecurityNeededNotification(account);
499 } else {
500 NotificationController.getInstance(mContext).showSecurityUnsupportedNotification(
501 account);
502 }
503 }
504
505 public static void clearAccountPolicy(Context context, Account account) {
506 setAccountPolicy(context, account, null, null);
507 }
508
509 /**
510 * Set the policy for an account atomically; this also removes any other policy associated with
511 * the account and sets the policy key for the account. If policy is null, the policyKey is
512 * set to 0 and the securitySyncKey to null. Also, update the account object to reflect the
513 * current policyKey and securitySyncKey
514 * @param context the caller's context
515 * @param account the account whose policy is to be set
516 * @param policy the policy to set, or null if we're clearing the policy
517 * @param securitySyncKey the security sync key for this account (ignored if policy is null)
518 */
519 public static void setAccountPolicy(Context context, Account account, Policy policy,
520 String securitySyncKey) {
521 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
522
523 // Make sure this is a valid policy set
524 if (policy != null) {
525 policy.normalize();
526 // Add the new policy (no account will yet reference this)
527 ops.add(ContentProviderOperation.newInsert(
528 Policy.CONTENT_URI).withValues(policy.toContentValues()).build());
529 // Make the policyKey of the account our newly created policy, and set the sync key
530 ops.add(ContentProviderOperation.newUpdate(
531 ContentUris.withAppendedId(Account.CONTENT_URI, account.mId))
532 .withValueBackReference(AccountColumns.POLICY_KEY, 0)
533 .withValue(AccountColumns.SECURITY_SYNC_KEY, securitySyncKey)
534 .build());
535 } else {
536 ops.add(ContentProviderOperation.newUpdate(
537 ContentUris.withAppendedId(Account.CONTENT_URI, account.mId))
538 .withValue(AccountColumns.SECURITY_SYNC_KEY, null)
539 .withValue(AccountColumns.POLICY_KEY, 0)
540 .build());
541 }
542
543 // Delete the previous policy associated with this account, if any
544 if (account.mPolicyKey > 0) {
545 ops.add(ContentProviderOperation.newDelete(
546 ContentUris.withAppendedId(
547 Policy.CONTENT_URI, account.mPolicyKey)).build());
548 }
549
550 try {
551 context.getContentResolver().applyBatch(EmailContent.AUTHORITY, ops);
552 account.refresh(context);
553 } catch (RemoteException e) {
554 // This is fatal to a remote process
555 throw new IllegalStateException("Exception setting account policy.");
556 } catch (OperationApplicationException e) {
557 // Can't happen; our provider doesn't throw this exception
558 }
559 }
560
561 public void setAccountPolicy(long accountId, Policy policy, String securityKey) {
562 Account account = Account.restoreAccountWithId(mContext, accountId);
563 Policy oldPolicy = null;
564 if (account.mPolicyKey > 0) {
565 oldPolicy = Policy.restorePolicyWithId(mContext, account.mPolicyKey);
566 }
567 boolean policyChanged = !oldPolicy.equals(policy);
568 if (!policyChanged && (TextUtilities.stringOrNullEquals(securityKey,
569 account.mSecuritySyncKey))) {
570 Log.d(Logging.LOG_TAG, "setAccountPolicy; policy unchanged");
571 } else {
572 setAccountPolicy(mContext, account, policy, securityKey);
573 policiesUpdated();
574 }
575
576 boolean setHold = false;
577 if (policy.mProtocolPoliciesUnsupported != null) {
578 // We can't support this, reasons in unsupportedRemotePolicies
579 Log.d(Logging.LOG_TAG,
580 "Notify policies for " + account.mDisplayName + " not supported.");
581 setHold = true;
582 NotificationController.getInstance(mContext).showSecurityUnsupportedNotification(
583 account);
584 // Erase data
585 Controller.getInstance(mContext).deleteSyncedDataSync(accountId);
586 } else if (isActive(policy)) {
587 if (policyChanged) {
588 Log.d(Logging.LOG_TAG, "Notify policies for " + account.mDisplayName + " changed.");
589 // Notify that policies changed
590 NotificationController.getInstance(mContext).showSecurityChangedNotification(
591 account);
592 }
593 } else {
594 setHold = true;
595 Log.d(Logging.LOG_TAG, "Notify policies for " + account.mDisplayName +
596 " are not being enforced.");
597 // Put up a notification
598 NotificationController.getInstance(mContext).showSecurityNeededNotification(account);
599 }
600 // Set/clear the account hold.
601 setAccountHoldFlag(mContext, account, setHold);
Andrew Stadler3d2b3b32010-02-05 11:10:39 -0800602 }
603
604 /**
605 * Called from the notification's intent receiver to register that the notification can be
606 * cleared now.
607 */
Marc Blankc6df1d62011-07-19 14:09:11 -0700608 public void clearNotification() {
Makoto Onuki308ce922011-03-21 17:08:16 -0700609 NotificationController.getInstance(mContext).cancelSecurityNeededNotification();
Andrew Stadler3d2b3b32010-02-05 11:10:39 -0800610 }
611
612 /**
Andrew Stadler50d16102010-02-09 11:01:01 -0800613 * API: Remote wipe (from server). This is final, there is no confirmation. It will only
Marc Blankc82c1ca2011-09-28 09:41:44 -0700614 * return to the caller if there is an unexpected failure. The wipe includes external storage.
Andrew Stadler3d2b3b32010-02-05 11:10:39 -0800615 */
Andrew Stadler50d16102010-02-09 11:01:01 -0800616 public void remoteWipe() {
Andrew Stadler3d2b3b32010-02-05 11:10:39 -0800617 DevicePolicyManager dpm = getDPM();
618 if (dpm.isAdminActive(mAdminName)) {
Marc Blankc82c1ca2011-09-28 09:41:44 -0700619 dpm.wipeData(DevicePolicyManager.WIPE_EXTERNAL_STORAGE);
Andrew Stadler50d16102010-02-09 11:01:01 -0800620 } else {
Marc Blank31d9acb2011-02-11 15:05:17 -0800621 Log.d(Logging.LOG_TAG, "Could not remote wipe because not device admin.");
Andrew Stadler3d2b3b32010-02-05 11:10:39 -0800622 }
Andrew Stadler345fb8b2010-01-26 17:24:15 -0800623 }
Andrew Stadler345fb8b2010-01-26 17:24:15 -0800624 /**
Andrew Stadler3d2b3b32010-02-05 11:10:39 -0800625 * If we are not the active device admin, try to become so.
626 *
Andy Stadlere7f4d3e2010-12-08 16:06:16 -0800627 * Also checks for any policies that we have added during the lifetime of this app.
628 * This catches the case where the user granted an earlier (smaller) set of policies
629 * but an app upgrade requires that new policies be granted.
630 *
Andrew Stadler3d2b3b32010-02-05 11:10:39 -0800631 * @return true if we are already active, false if we are not
Andrew Stadler345fb8b2010-01-26 17:24:15 -0800632 */
Andrew Stadler3d2b3b32010-02-05 11:10:39 -0800633 public boolean isActiveAdmin() {
634 DevicePolicyManager dpm = getDPM();
Andy Stadlerc2e63832011-01-17 12:54:40 -0800635 return dpm.isAdminActive(mAdminName)
636 && dpm.hasGrantedPolicy(mAdminName, DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD)
Ben Komaloaa0a3552011-06-16 14:40:15 -0700637 && dpm.hasGrantedPolicy(mAdminName, DeviceAdminInfo.USES_ENCRYPTED_STORAGE)
638 && dpm.hasGrantedPolicy(mAdminName, DeviceAdminInfo.USES_POLICY_DISABLE_CAMERA);
Andrew Stadler3d2b3b32010-02-05 11:10:39 -0800639 }
Andrew Stadler345fb8b2010-01-26 17:24:15 -0800640
Andrew Stadler3d2b3b32010-02-05 11:10:39 -0800641 /**
642 * Report admin component name - for making calls into device policy manager
643 */
644 public ComponentName getAdminComponent() {
645 return mAdminName;
646 }
647
648 /**
Marc Blank02d59d22010-10-25 11:49:29 -0700649 * Delete all accounts whose security flags aren't zero (i.e. they have security enabled).
650 * This method is synchronous, so it should normally be called within a worker thread (the
651 * exception being for unit tests)
652 *
653 * @param context the caller's context
Andrew Stadler3d2b3b32010-02-05 11:10:39 -0800654 */
Marc Blank02d59d22010-10-25 11:49:29 -0700655 /*package*/ void deleteSecuredAccounts(Context context) {
656 ContentResolver cr = context.getContentResolver();
657 // Find all accounts with security and delete them
658 Cursor c = cr.query(Account.CONTENT_URI, EmailContent.ID_PROJECTION,
Marc Blankaeee10e2011-04-27 17:12:06 -0700659 Account.SECURITY_NONZERO_SELECTION, null, null);
Marc Blank02d59d22010-10-25 11:49:29 -0700660 try {
661 Log.w(TAG, "Email administration disabled; deleting " + c.getCount() +
662 " secured account(s)");
663 while (c.moveToNext()) {
664 Controller.getInstance(context).deleteAccountSync(
665 c.getLong(EmailContent.ID_PROJECTION_COLUMN), context);
666 }
667 } finally {
668 c.close();
669 }
Marc Blank2736c1a2011-10-20 10:13:02 -0700670 policiesUpdated();
Marc Blank02d59d22010-10-25 11:49:29 -0700671 }
672
673 /**
674 * Internal handler for enabled->disabled transitions. Deletes all secured accounts.
Andy Stadlera2269e82010-12-30 00:16:55 -0800675 * Must call from worker thread, not on UI thread.
Marc Blank02d59d22010-10-25 11:49:29 -0700676 */
677 /*package*/ void onAdminEnabled(boolean isEnabled) {
Andrew Stadler856e09d2010-04-06 22:17:21 -0700678 if (!isEnabled) {
Andy Stadlera2269e82010-12-30 00:16:55 -0800679 deleteSecuredAccounts(mContext);
Andrew Stadler3d2b3b32010-02-05 11:10:39 -0800680 }
Andrew Stadler3d2b3b32010-02-05 11:10:39 -0800681 }
682
683 /**
Andy Stadler1ca111c2010-12-01 12:58:36 -0800684 * Handle password expiration - if any accounts appear to have triggered this, put up
685 * warnings, or even shut them down.
686 *
687 * NOTE: If there are multiple accounts with password expiration policies, the device
688 * password will be set to expire in the shortest required interval (most secure). The logic
689 * in this method operates based on the aggregate setting - irrespective of which account caused
690 * the expiration. In other words, all accounts (that require expiration) will run/stop
691 * based on the requirements of the account with the shortest interval.
692 */
Andy Stadlera2269e82010-12-30 00:16:55 -0800693 private void onPasswordExpiring(Context context) {
Andy Stadler1ca111c2010-12-01 12:58:36 -0800694 // 1. Do we have any accounts that matter here?
695 long nextExpiringAccountId = findShortestExpiration(context);
696
697 // 2. If not, exit immediately
698 if (nextExpiringAccountId == -1) {
699 return;
700 }
701
702 // 3. If yes, are we warning or expired?
703 long expirationDate = getDPM().getPasswordExpiration(mAdminName);
704 long timeUntilExpiration = expirationDate - System.currentTimeMillis();
705 boolean expired = timeUntilExpiration < 0;
706 if (!expired) {
707 // 4. If warning, simply put up a generic notification and report that it came from
708 // the shortest-expiring account.
Makoto Onuki308ce922011-03-21 17:08:16 -0700709 NotificationController.getInstance(mContext).showPasswordExpiringNotification(
710 nextExpiringAccountId);
Andy Stadler1ca111c2010-12-01 12:58:36 -0800711 } else {
712 // 5. Actually expired - find all accounts that expire passwords, and wipe them
713 boolean wiped = wipeExpiredAccounts(context, Controller.getInstance(context));
714 if (wiped) {
Makoto Onuki308ce922011-03-21 17:08:16 -0700715 NotificationController.getInstance(mContext).showPasswordExpiredNotification(
716 nextExpiringAccountId);
Andy Stadler1ca111c2010-12-01 12:58:36 -0800717 }
718 }
719 }
720
721 /**
722 * Find the account with the shortest expiration time. This is always assumed to be
723 * the account that forces the password to be refreshed.
724 * @return -1 if no expirations, or accountId if one is found
725 */
Marc Blankaeee10e2011-04-27 17:12:06 -0700726 @VisibleForTesting
727 /*package*/ static long findShortestExpiration(Context context) {
728 long policyId = Utility.getFirstRowLong(context, Policy.CONTENT_URI, Policy.ID_PROJECTION,
729 HAS_PASSWORD_EXPIRATION, null, PolicyColumns.PASSWORD_EXPIRATION_DAYS + " ASC",
730 EmailContent.ID_PROJECTION_COLUMN, -1L);
731 if (policyId < 0) return -1L;
732 return Policy.getAccountIdWithPolicyKey(context, policyId);
Andy Stadler1ca111c2010-12-01 12:58:36 -0800733 }
734
735 /**
736 * For all accounts that require password expiration, put them in security hold and wipe
737 * their data.
738 * @param context
739 * @param controller
740 * @return true if one or more accounts were wiped
741 */
Marc Blankaeee10e2011-04-27 17:12:06 -0700742 @VisibleForTesting
743 /*package*/ static boolean wipeExpiredAccounts(Context context, Controller controller) {
Andy Stadler1ca111c2010-12-01 12:58:36 -0800744 boolean result = false;
Marc Blankaeee10e2011-04-27 17:12:06 -0700745 Cursor c = context.getContentResolver().query(Policy.CONTENT_URI,
746 Policy.ID_PROJECTION, HAS_PASSWORD_EXPIRATION, null, null);
Andy Stadler1ca111c2010-12-01 12:58:36 -0800747 try {
748 while (c.moveToNext()) {
Marc Blankaeee10e2011-04-27 17:12:06 -0700749 long policyId = c.getLong(Policy.ID_PROJECTION_COLUMN);
750 long accountId = Policy.getAccountIdWithPolicyKey(context, policyId);
751 if (accountId < 0) continue;
752 Account account = Account.restoreAccountWithId(context, accountId);
753 if (account != null) {
754 // Mark the account as "on hold".
755 setAccountHoldFlag(context, account, true);
756 // Erase data
757 controller.deleteSyncedDataSync(accountId);
758 // Report one or more were found
759 result = true;
Andy Stadler1ca111c2010-12-01 12:58:36 -0800760 }
761 }
762 } finally {
763 c.close();
764 }
765 return result;
766 }
767
768 /**
Andy Stadlera2269e82010-12-30 00:16:55 -0800769 * Callback from EmailBroadcastProcessorService. This provides the workers for the
770 * DeviceAdminReceiver calls. These should perform the work directly and not use async
771 * threads for completion.
772 */
773 public static void onDeviceAdminReceiverMessage(Context context, int message) {
774 SecurityPolicy instance = SecurityPolicy.getInstance(context);
775 switch (message) {
776 case DEVICE_ADMIN_MESSAGE_ENABLED:
777 instance.onAdminEnabled(true);
778 break;
779 case DEVICE_ADMIN_MESSAGE_DISABLED:
780 instance.onAdminEnabled(false);
781 break;
782 case DEVICE_ADMIN_MESSAGE_PASSWORD_CHANGED:
783 // TODO make a small helper for this
784 // Clear security holds (if any)
785 Account.clearSecurityHoldOnAllAccounts(context);
786 // Cancel any active notifications (if any are posted)
Makoto Onuki308ce922011-03-21 17:08:16 -0700787 NotificationController.getInstance(context).cancelPasswordExpirationNotifications();
Andy Stadlera2269e82010-12-30 00:16:55 -0800788 break;
789 case DEVICE_ADMIN_MESSAGE_PASSWORD_EXPIRING:
790 instance.onPasswordExpiring(instance.mContext);
791 break;
792 }
793 }
794
795 /**
Andrew Stadler3d2b3b32010-02-05 11:10:39 -0800796 * Device Policy administrator. This is primarily a listener for device state changes.
797 * Note: This is instantiated by incoming messages.
Andy Stadlera2269e82010-12-30 00:16:55 -0800798 * Note: This is actually a BroadcastReceiver and must remain within the guidelines required
799 * for proper behavior, including avoidance of ANRs.
Andrew Stadler5893e9e2010-02-08 23:09:05 -0800800 * Note: We do not implement onPasswordFailed() because the default behavior of the
801 * DevicePolicyManager - complete local wipe after 'n' failures - is sufficient.
Andrew Stadler3d2b3b32010-02-05 11:10:39 -0800802 */
Dianne Hackborn4ae83c52010-02-16 20:40:32 -0800803 public static class PolicyAdmin extends DeviceAdminReceiver {
Andrew Stadler345fb8b2010-01-26 17:24:15 -0800804
805 /**
806 * Called after the administrator is first enabled.
807 */
808 @Override
809 public void onEnabled(Context context, Intent intent) {
Andy Stadlera2269e82010-12-30 00:16:55 -0800810 EmailBroadcastProcessorService.processDevicePolicyMessage(context,
811 DEVICE_ADMIN_MESSAGE_ENABLED);
Andrew Stadler345fb8b2010-01-26 17:24:15 -0800812 }
Andrew Stadler856e09d2010-04-06 22:17:21 -0700813
Andrew Stadler345fb8b2010-01-26 17:24:15 -0800814 /**
815 * Called prior to the administrator being disabled.
816 */
817 @Override
818 public void onDisabled(Context context, Intent intent) {
Andy Stadlera2269e82010-12-30 00:16:55 -0800819 EmailBroadcastProcessorService.processDevicePolicyMessage(context,
820 DEVICE_ADMIN_MESSAGE_DISABLED);
Andrew Stadler345fb8b2010-01-26 17:24:15 -0800821 }
Andrew Stadler856e09d2010-04-06 22:17:21 -0700822
Andrew Stadler345fb8b2010-01-26 17:24:15 -0800823 /**
Marc Blank02d59d22010-10-25 11:49:29 -0700824 * Called when the user asks to disable administration; we return a warning string that
825 * will be presented to the user
826 */
827 @Override
828 public CharSequence onDisableRequested(Context context, Intent intent) {
829 return context.getString(R.string.disable_admin_warning);
830 }
831
832 /**
Andrew Stadler345fb8b2010-01-26 17:24:15 -0800833 * Called after the user has changed their password.
834 */
835 @Override
836 public void onPasswordChanged(Context context, Intent intent) {
Andy Stadlera2269e82010-12-30 00:16:55 -0800837 EmailBroadcastProcessorService.processDevicePolicyMessage(context,
838 DEVICE_ADMIN_MESSAGE_PASSWORD_CHANGED);
Andy Stadler1ca111c2010-12-01 12:58:36 -0800839 }
840
841 /**
842 * Called when device password is expiring
843 */
844 @Override
845 public void onPasswordExpiring(Context context, Intent intent) {
Andy Stadlera2269e82010-12-30 00:16:55 -0800846 EmailBroadcastProcessorService.processDevicePolicyMessage(context,
847 DEVICE_ADMIN_MESSAGE_PASSWORD_EXPIRING);
Andrew Stadler345fb8b2010-01-26 17:24:15 -0800848 }
Andrew Stadler345fb8b2010-01-26 17:24:15 -0800849 }
850}