• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.email;
18 
19 import com.android.email.activity.setup.AccountSecurity;
20 import com.android.email.provider.EmailContent;
21 import com.android.email.provider.EmailContent.Account;
22 import com.android.email.provider.EmailContent.AccountColumns;
23 import com.android.email.service.MailService;
24 
25 import android.app.Notification;
26 import android.app.NotificationManager;
27 import android.app.PendingIntent;
28 import android.app.admin.DeviceAdminReceiver;
29 import android.app.admin.DevicePolicyManager;
30 import android.content.ComponentName;
31 import android.content.ContentResolver;
32 import android.content.ContentUris;
33 import android.content.ContentValues;
34 import android.content.Context;
35 import android.content.Intent;
36 import android.database.Cursor;
37 import android.media.AudioManager;
38 import android.net.Uri;
39 import android.util.Log;
40 
41 /**
42  * Utility functions to support reading and writing security policies, and handshaking the device
43  * into and out of various security states.
44  */
45 public class SecurityPolicy {
46 
47     private static SecurityPolicy sInstance = null;
48     private Context mContext;
49     private DevicePolicyManager mDPM;
50     private ComponentName mAdminName;
51     private PolicySet mAggregatePolicy;
52 
53     /* package */ static final PolicySet NO_POLICY_SET =
54             new PolicySet(0, PolicySet.PASSWORD_MODE_NONE, 0, 0, false);
55 
56     /**
57      * This projection on Account is for scanning/reading
58      */
59     private static final String[] ACCOUNT_SECURITY_PROJECTION = new String[] {
60         AccountColumns.ID, AccountColumns.SECURITY_FLAGS
61     };
62     private static final int ACCOUNT_SECURITY_COLUMN_FLAGS = 1;
63     // Note, this handles the NULL case to deal with older accounts where the column was added
64     private static final String WHERE_ACCOUNT_SECURITY_NONZERO =
65         Account.SECURITY_FLAGS + " IS NOT NULL AND " + Account.SECURITY_FLAGS + "!=0";
66 
67     /**
68      * This projection on Account is for clearing the "security hold" column.  Also includes
69      * the security flags column, so we can use it for selecting.
70      */
71     private static final String[] ACCOUNT_FLAGS_PROJECTION = new String[] {
72         AccountColumns.ID, AccountColumns.FLAGS, AccountColumns.SECURITY_FLAGS
73     };
74     private static final int ACCOUNT_FLAGS_COLUMN_ID = 0;
75     private static final int ACCOUNT_FLAGS_COLUMN_FLAGS = 1;
76 
77     /**
78      * Get the security policy instance
79      */
getInstance(Context context)80     public synchronized static SecurityPolicy getInstance(Context context) {
81         if (sInstance == null) {
82             sInstance = new SecurityPolicy(context);
83         }
84         return sInstance;
85     }
86 
87     /**
88      * Private constructor (one time only)
89      */
SecurityPolicy(Context context)90     private SecurityPolicy(Context context) {
91         mContext = context;
92         mDPM = null;
93         mAdminName = new ComponentName(context, PolicyAdmin.class);
94         mAggregatePolicy = null;
95     }
96 
97     /**
98      * For testing only: Inject context into already-created instance
99      */
setContext(Context context)100     /* package */ void setContext(Context context) {
101         mContext = context;
102     }
103 
104     /**
105      * Compute the aggregate policy for all accounts that require it, and record it.
106      *
107      * The business logic is as follows:
108      *  min password length         take the max
109      *  password mode               take the max (strongest mode)
110      *  max password fails          take the min
111      *  max screen lock time        take the min
112      *  require remote wipe         take the max (logical or)
113      *
114      * @return a policy representing the strongest aggregate.  If no policy sets are defined,
115      * a lightweight "nothing required" policy will be returned.  Never null.
116      */
computeAggregatePolicy()117     /* package */ PolicySet computeAggregatePolicy() {
118         boolean policiesFound = false;
119 
120         int minPasswordLength = Integer.MIN_VALUE;
121         int passwordMode = Integer.MIN_VALUE;
122         int maxPasswordFails = Integer.MAX_VALUE;
123         int maxScreenLockTime = Integer.MAX_VALUE;
124         boolean requireRemoteWipe = false;
125 
126         Cursor c = mContext.getContentResolver().query(Account.CONTENT_URI,
127                 ACCOUNT_SECURITY_PROJECTION, WHERE_ACCOUNT_SECURITY_NONZERO, null, null);
128         try {
129             while (c.moveToNext()) {
130                 int flags = c.getInt(ACCOUNT_SECURITY_COLUMN_FLAGS);
131                 if (flags != 0) {
132                     PolicySet p = new PolicySet(flags);
133                     minPasswordLength = Math.max(p.mMinPasswordLength, minPasswordLength);
134                     passwordMode  = Math.max(p.mPasswordMode, passwordMode);
135                     if (p.mMaxPasswordFails > 0) {
136                         maxPasswordFails = Math.min(p.mMaxPasswordFails, maxPasswordFails);
137                     }
138                     if (p.mMaxScreenLockTime > 0) {
139                         maxScreenLockTime = Math.min(p.mMaxScreenLockTime, maxScreenLockTime);
140                     }
141                     requireRemoteWipe |= p.mRequireRemoteWipe;
142                     policiesFound = true;
143                 }
144             }
145         } finally {
146             c.close();
147         }
148         if (policiesFound) {
149             // final cleanup pass converts any untouched min/max values to zero (not specified)
150             if (minPasswordLength == Integer.MIN_VALUE) minPasswordLength = 0;
151             if (passwordMode == Integer.MIN_VALUE) passwordMode = 0;
152             if (maxPasswordFails == Integer.MAX_VALUE) maxPasswordFails = 0;
153             if (maxScreenLockTime == Integer.MAX_VALUE) maxScreenLockTime = 0;
154 
155             return new PolicySet(minPasswordLength, passwordMode, maxPasswordFails,
156                     maxScreenLockTime, requireRemoteWipe);
157         } else {
158             return NO_POLICY_SET;
159         }
160     }
161 
162     /**
163      * Return updated aggregate policy, from cached value if possible
164      */
getAggregatePolicy()165     public synchronized PolicySet getAggregatePolicy() {
166         if (mAggregatePolicy == null) {
167             mAggregatePolicy = computeAggregatePolicy();
168         }
169         return mAggregatePolicy;
170     }
171 
172     /**
173      * Get the dpm.  This mainly allows us to make some utility calls without it, for testing.
174      */
getDPM()175     private synchronized DevicePolicyManager getDPM() {
176         if (mDPM == null) {
177             mDPM = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
178         }
179         return mDPM;
180     }
181 
182     /**
183      * API: Report that policies may have been updated due to rewriting values in an Account.
184      * @param accountId the account that has been updated, -1 if unknown/deleted
185      */
updatePolicies(long accountId)186     public synchronized void updatePolicies(long accountId) {
187         mAggregatePolicy = null;
188     }
189 
190     /**
191      * API: Report that policies may have been updated *and* the caller vouches that the
192      * change is a reduction in policies.  This forces an immediate change to device state.
193      * Typically used when deleting accounts, although we may use it for server-side policy
194      * rollbacks.
195      */
reducePolicies()196     public void reducePolicies() {
197         updatePolicies(-1);
198         setActivePolicies();
199     }
200 
201     /**
202      * API: Query used to determine if a given policy is "active" (the device is operating at
203      * the required security level).
204      *
205      * This can be used when syncing a specific account, by passing a specific set of policies
206      * for that account.  Or, it can be used at any time to compare the device
207      * state against the aggregate set of device policies stored in all accounts.
208      *
209      * This method is for queries only, and does not trigger any change in device state.
210      *
211      * @param policies the policies requested, or null to check aggregate stored policies
212      * @return true if the policies are active, false if not active
213      */
isActive(PolicySet policies)214     public boolean isActive(PolicySet policies) {
215         // select aggregate set if needed
216         if (policies == null) {
217             policies = getAggregatePolicy();
218         }
219         // quick check for the "empty set" of no policies
220         if (policies == NO_POLICY_SET) {
221             return true;
222         }
223         DevicePolicyManager dpm = getDPM();
224         if (dpm.isAdminActive(mAdminName)) {
225             // check each policy explicitly
226             if (policies.mMinPasswordLength > 0) {
227                 if (dpm.getPasswordMinimumLength(mAdminName) < policies.mMinPasswordLength) {
228                     return false;
229                 }
230             }
231             if (policies.mPasswordMode > 0) {
232                 if (dpm.getPasswordQuality(mAdminName) < policies.getDPManagerPasswordQuality()) {
233                     return false;
234                 }
235                 if (!dpm.isActivePasswordSufficient()) {
236                     return false;
237                 }
238             }
239             if (policies.mMaxScreenLockTime > 0) {
240                 // Note, we use seconds, dpm uses milliseconds
241                 if (dpm.getMaximumTimeToLock(mAdminName) > policies.mMaxScreenLockTime * 1000) {
242                     return false;
243                 }
244             }
245             // password failures are counted locally - no test required here
246             // no check required for remote wipe (it's supported, if we're the admin)
247 
248             // making it this far means we passed!
249             return true;
250         }
251         // return false, not active
252         return false;
253     }
254 
255     /**
256      * Set the requested security level based on the aggregate set of requests.
257      * If the set is empty, we release our device administration.  If the set is non-empty,
258      * we only proceed if we are already active as an admin.
259      */
setActivePolicies()260     public void setActivePolicies() {
261         DevicePolicyManager dpm = getDPM();
262         // compute aggregate set of policies
263         PolicySet policies = getAggregatePolicy();
264         // if empty set, detach from policy manager
265         if (policies == NO_POLICY_SET) {
266             dpm.removeActiveAdmin(mAdminName);
267         } else if (dpm.isAdminActive(mAdminName)) {
268             // set each policy in the policy manager
269             // password mode & length
270             dpm.setPasswordQuality(mAdminName, policies.getDPManagerPasswordQuality());
271             dpm.setPasswordMinimumLength(mAdminName, policies.mMinPasswordLength);
272             // screen lock time
273             dpm.setMaximumTimeToLock(mAdminName, policies.mMaxScreenLockTime * 1000);
274             // local wipe (failed passwords limit)
275             dpm.setMaximumFailedPasswordsForWipe(mAdminName, policies.mMaxPasswordFails);
276         }
277     }
278 
279     /**
280      * API: Set/Clear the "hold" flag in any account.  This flag serves a dual purpose:
281      * Setting it gives us an indication that it was blocked, and clearing it gives EAS a
282      * signal to try syncing again.
283      */
setAccountHoldFlag(Account account, boolean newState)284     public void setAccountHoldFlag(Account account, boolean newState) {
285         if (newState) {
286             account.mFlags |= Account.FLAGS_SECURITY_HOLD;
287         } else {
288             account.mFlags &= ~Account.FLAGS_SECURITY_HOLD;
289         }
290         ContentValues cv = new ContentValues();
291         cv.put(AccountColumns.FLAGS, account.mFlags);
292         account.update(mContext, cv);
293     }
294 
295     /**
296      * Clear all account hold flags that are set.  This will trigger watchers, and in particular
297      * will cause EAS to try and resync the account(s).
298      */
clearAccountHoldFlags()299     public void clearAccountHoldFlags() {
300         ContentResolver resolver = mContext.getContentResolver();
301         Cursor c = resolver.query(Account.CONTENT_URI, ACCOUNT_FLAGS_PROJECTION,
302                 WHERE_ACCOUNT_SECURITY_NONZERO, null, null);
303         try {
304             while (c.moveToNext()) {
305                 int flags = c.getInt(ACCOUNT_FLAGS_COLUMN_FLAGS);
306                 if (0 != (flags & Account.FLAGS_SECURITY_HOLD)) {
307                     ContentValues cv = new ContentValues();
308                     cv.put(AccountColumns.FLAGS, flags & ~Account.FLAGS_SECURITY_HOLD);
309                     long accountId = c.getLong(ACCOUNT_FLAGS_COLUMN_ID);
310                     Uri uri = ContentUris.withAppendedId(Account.CONTENT_URI, accountId);
311                     resolver.update(uri, cv, null, null);
312                 }
313             }
314         } finally {
315             c.close();
316         }
317     }
318 
319     /**
320      * API: Sync service should call this any time a sync fails due to isActive() returning false.
321      * This will kick off the notify-acquire-admin-state process and/or increase the security level.
322      * The caller needs to write the required policies into this account before making this call.
323      * Should not be called from UI thread - uses DB lookups to prepare new notifications
324      *
325      * @param accountId the account for which sync cannot proceed
326      */
policiesRequired(long accountId)327     public void policiesRequired(long accountId) {
328         Account account = EmailContent.Account.restoreAccountWithId(mContext, accountId);
329 
330         // Mark the account as "on hold".
331         setAccountHoldFlag(account, true);
332 
333         // Put up a notification
334         String tickerText = mContext.getString(R.string.security_notification_ticker_fmt,
335                 account.getDisplayName());
336         String contentTitle = mContext.getString(R.string.security_notification_content_title);
337         String contentText = account.getDisplayName();
338         String ringtoneString = account.getRingtone();
339         Uri ringTone = (ringtoneString == null) ? null : Uri.parse(ringtoneString);
340         boolean vibrate = 0 != (account.mFlags & Account.FLAGS_VIBRATE_ALWAYS);
341         boolean vibrateWhenSilent = 0 != (account.mFlags & Account.FLAGS_VIBRATE_WHEN_SILENT);
342 
343         Intent intent = AccountSecurity.actionUpdateSecurityIntent(mContext, accountId);
344         PendingIntent pending =
345             PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
346 
347         Notification notification = new Notification(R.drawable.stat_notify_email_generic,
348                 tickerText, System.currentTimeMillis());
349         notification.setLatestEventInfo(mContext, contentTitle, contentText, pending);
350 
351         // Use the account's notification rules for sound & vibrate (but always notify)
352         AudioManager audioManager =
353             (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
354         boolean nowSilent =
355             audioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE;
356         notification.sound = ringTone;
357 
358         if (vibrate || (vibrateWhenSilent && nowSilent)) {
359             notification.defaults |= Notification.DEFAULT_VIBRATE;
360         }
361         notification.flags |= Notification.FLAG_SHOW_LIGHTS;
362         notification.defaults |= Notification.DEFAULT_LIGHTS;
363 
364         NotificationManager notificationManager =
365             (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
366         notificationManager.notify(MailService.NOTIFICATION_ID_SECURITY_NEEDED, notification);
367     }
368 
369     /**
370      * Called from the notification's intent receiver to register that the notification can be
371      * cleared now.
372      */
clearNotification(long accountId)373     public void clearNotification(long accountId) {
374         NotificationManager notificationManager =
375             (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
376         notificationManager.cancel(MailService.NOTIFICATION_ID_SECURITY_NEEDED);
377     }
378 
379     /**
380      * API: Remote wipe (from server).  This is final, there is no confirmation.  It will only
381      * return to the caller if there is an unexpected failure.
382      */
remoteWipe()383     public void remoteWipe() {
384         DevicePolicyManager dpm = getDPM();
385         if (dpm.isAdminActive(mAdminName)) {
386             dpm.wipeData(0);
387         } else {
388             Log.d(Email.LOG_TAG, "Could not remote wipe because not device admin.");
389         }
390     }
391 
392     /**
393      * Class for tracking policies and reading/writing into accounts
394      */
395     public static class PolicySet {
396 
397         // Security (provisioning) flags
398             // bits 0..4: password length (0=no password required)
399         private static final int PASSWORD_LENGTH_MASK = 31;
400         private static final int PASSWORD_LENGTH_SHIFT = 0;
401         public static final int PASSWORD_LENGTH_MAX = 30;
402             // bits 5..8: password mode
403         private static final int PASSWORD_MODE_SHIFT = 5;
404         private static final int PASSWORD_MODE_MASK = 15 << PASSWORD_MODE_SHIFT;
405         public static final int PASSWORD_MODE_NONE = 0 << PASSWORD_MODE_SHIFT;
406         public static final int PASSWORD_MODE_SIMPLE = 1 << PASSWORD_MODE_SHIFT;
407         public static final int PASSWORD_MODE_STRONG = 2 << PASSWORD_MODE_SHIFT;
408             // bits 9..13: password failures -> wipe device (0=disabled)
409         private static final int PASSWORD_MAX_FAILS_SHIFT = 9;
410         private static final int PASSWORD_MAX_FAILS_MASK = 31 << PASSWORD_MAX_FAILS_SHIFT;
411         public static final int PASSWORD_MAX_FAILS_MAX = 31;
412             // bits 14..24: seconds to screen lock (0=not required)
413         private static final int SCREEN_LOCK_TIME_SHIFT = 14;
414         private static final int SCREEN_LOCK_TIME_MASK = 2047 << SCREEN_LOCK_TIME_SHIFT;
415         public static final int SCREEN_LOCK_TIME_MAX = 2047;
416             // bit 25: remote wipe capability required
417         private static final int REQUIRE_REMOTE_WIPE = 1 << 25;
418 
419         /*package*/ final int mMinPasswordLength;
420         /*package*/ final int mPasswordMode;
421         /*package*/ final int mMaxPasswordFails;
422         /*package*/ final int mMaxScreenLockTime;
423         /*package*/ final boolean mRequireRemoteWipe;
424 
getMinPasswordLengthForTest()425         public int getMinPasswordLengthForTest() {
426             return mMinPasswordLength;
427         }
428 
getPasswordModeForTest()429         public int getPasswordModeForTest() {
430             return mPasswordMode;
431         }
432 
getMaxPasswordFailsForTest()433         public int getMaxPasswordFailsForTest() {
434             return mMaxPasswordFails;
435         }
436 
getMaxScreenLockTimeForTest()437         public int getMaxScreenLockTimeForTest() {
438             return mMaxScreenLockTime;
439         }
440 
isRequireRemoteWipeForTest()441         public boolean isRequireRemoteWipeForTest() {
442             return mRequireRemoteWipe;
443         }
444 
445         /**
446          * Create from raw values.
447          * @param minPasswordLength (0=not enforced)
448          * @param passwordMode
449          * @param maxPasswordFails (0=not enforced)
450          * @param maxScreenLockTime in seconds (0=not enforced)
451          * @param requireRemoteWipe
452          * @throws IllegalArgumentException for illegal arguments.
453          */
PolicySet(int minPasswordLength, int passwordMode, int maxPasswordFails, int maxScreenLockTime, boolean requireRemoteWipe)454         public PolicySet(int minPasswordLength, int passwordMode, int maxPasswordFails,
455                 int maxScreenLockTime, boolean requireRemoteWipe) throws IllegalArgumentException {
456             // If we're not enforcing passwords, make sure we clean up related values, since EAS
457             // can send non-zero values for any or all of these
458             if (passwordMode == PASSWORD_MODE_NONE) {
459                 maxPasswordFails = 0;
460                 maxScreenLockTime = 0;
461                 minPasswordLength = 0;
462             } else {
463                 if ((passwordMode != PASSWORD_MODE_SIMPLE) &&
464                         (passwordMode != PASSWORD_MODE_STRONG)) {
465                     throw new IllegalArgumentException("password mode");
466                 }
467                 // The next value has a hard limit which cannot be supported if exceeded.
468                 if (minPasswordLength > PASSWORD_LENGTH_MAX) {
469                     throw new IllegalArgumentException("password length");
470                 }
471                 // This value can be reduced (which actually increases security) if necessary
472                 if (maxPasswordFails > PASSWORD_MAX_FAILS_MAX) {
473                     maxPasswordFails = PASSWORD_MAX_FAILS_MAX;
474                 }
475                 // This value can be reduced (which actually increases security) if necessary
476                 if (maxScreenLockTime > SCREEN_LOCK_TIME_MAX) {
477                     maxScreenLockTime = SCREEN_LOCK_TIME_MAX;
478                 }
479             }
480             mMinPasswordLength = minPasswordLength;
481             mPasswordMode = passwordMode;
482             mMaxPasswordFails = maxPasswordFails;
483             mMaxScreenLockTime = maxScreenLockTime;
484             mRequireRemoteWipe = requireRemoteWipe;
485         }
486 
487         /**
488          * Create from values encoded in an account
489          * @param account
490          */
PolicySet(Account account)491         public PolicySet(Account account) {
492             this(account.mSecurityFlags);
493         }
494 
495         /**
496          * Create from values encoded in an account flags int
497          */
PolicySet(int flags)498         public PolicySet(int flags) {
499             mMinPasswordLength =
500                 (flags & PASSWORD_LENGTH_MASK) >> PASSWORD_LENGTH_SHIFT;
501             mPasswordMode =
502                 (flags & PASSWORD_MODE_MASK);
503             mMaxPasswordFails =
504                 (flags & PASSWORD_MAX_FAILS_MASK) >> PASSWORD_MAX_FAILS_SHIFT;
505             mMaxScreenLockTime =
506                 (flags & SCREEN_LOCK_TIME_MASK) >> SCREEN_LOCK_TIME_SHIFT;
507             mRequireRemoteWipe = 0 != (flags & REQUIRE_REMOTE_WIPE);
508         }
509 
510         /**
511          * Helper to map our internal encoding to DevicePolicyManager password modes.
512          */
getDPManagerPasswordQuality()513         public int getDPManagerPasswordQuality() {
514             switch (mPasswordMode) {
515                 case PASSWORD_MODE_SIMPLE:
516                     return DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
517                 case PASSWORD_MODE_STRONG:
518                     return DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
519                 default:
520                     return DevicePolicyManager .PASSWORD_QUALITY_UNSPECIFIED;
521             }
522         }
523 
524         /**
525          * Record flags (and a sync key for the flags) into an Account
526          * Note: the hash code is defined as the encoding used in Account
527          *
528          * @param account to write the values mSecurityFlags and mSecuritySyncKey
529          * @param syncKey the value to write into the account's mSecuritySyncKey
530          * @param update if true, also writes the account back to the provider (updating only
531          *  the fields changed by this API)
532          * @param context a context for writing to the provider
533          * @return true if the actual policies changed, false if no change (note, sync key
534          *  does not affect this)
535          */
writeAccount(Account account, String syncKey, boolean update, Context context)536         public boolean writeAccount(Account account, String syncKey, boolean update,
537                 Context context) {
538             int newFlags = hashCode();
539             boolean dirty = (newFlags != account.mSecurityFlags);
540             account.mSecurityFlags = newFlags;
541             account.mSecuritySyncKey = syncKey;
542             if (update) {
543                 if (account.isSaved()) {
544                     ContentValues cv = new ContentValues();
545                     cv.put(AccountColumns.SECURITY_FLAGS, account.mSecurityFlags);
546                     cv.put(AccountColumns.SECURITY_SYNC_KEY, account.mSecuritySyncKey);
547                     account.update(context, cv);
548                 } else {
549                     account.save(context);
550                 }
551             }
552             return dirty;
553         }
554 
555         @Override
equals(Object o)556         public boolean equals(Object o) {
557             if (o instanceof PolicySet) {
558                 PolicySet other = (PolicySet)o;
559                 return (this.mMinPasswordLength == other.mMinPasswordLength)
560                         && (this.mPasswordMode == other.mPasswordMode)
561                         && (this.mMaxPasswordFails == other.mMaxPasswordFails)
562                         && (this.mMaxScreenLockTime == other.mMaxScreenLockTime)
563                         && (this.mRequireRemoteWipe == other.mRequireRemoteWipe);
564             }
565             return false;
566         }
567 
568         /**
569          * Note: the hash code is defined as the encoding used in Account
570          */
571         @Override
hashCode()572         public int hashCode() {
573             int flags = 0;
574             flags = mMinPasswordLength << PASSWORD_LENGTH_SHIFT;
575             flags |= mPasswordMode;
576             flags |= mMaxPasswordFails << PASSWORD_MAX_FAILS_SHIFT;
577             flags |= mMaxScreenLockTime << SCREEN_LOCK_TIME_SHIFT;
578             if (mRequireRemoteWipe) {
579                 flags |= REQUIRE_REMOTE_WIPE;
580             }
581             return flags;
582         }
583 
584         @Override
toString()585         public String toString() {
586             return "{ " + "pw-len-min=" + mMinPasswordLength + " pw-mode=" + mPasswordMode
587                     + " pw-fails-max=" + mMaxPasswordFails + " screenlock-max="
588                     + mMaxScreenLockTime + " remote-wipe-req=" + mRequireRemoteWipe + "}";
589         }
590     }
591 
592     /**
593      * If we are not the active device admin, try to become so.
594      *
595      * @return true if we are already active, false if we are not
596      */
isActiveAdmin()597     public boolean isActiveAdmin() {
598         DevicePolicyManager dpm = getDPM();
599         return dpm.isAdminActive(mAdminName);
600     }
601 
602     /**
603      * Report admin component name - for making calls into device policy manager
604      */
getAdminComponent()605     public ComponentName getAdminComponent() {
606         return mAdminName;
607     }
608 
609     /**
610      * Internal handler for enabled->disabled transitions.  Resets all security keys
611      * forcing EAS to resync security state.
612      */
onAdminEnabled(boolean isEnabled)613     /* package */ void onAdminEnabled(boolean isEnabled) {
614         if (!isEnabled) {
615             // transition to disabled state
616             // Response:  clear *all* security state information from the accounts, forcing
617             // them back to the initial configurations requiring policy administration
618             ContentValues cv = new ContentValues();
619             cv.put(AccountColumns.SECURITY_FLAGS, 0);
620             cv.putNull(AccountColumns.SECURITY_SYNC_KEY);
621             mContext.getContentResolver().update(Account.CONTENT_URI, cv, null, null);
622             updatePolicies(-1);
623         }
624     }
625 
626     /**
627      * Device Policy administrator.  This is primarily a listener for device state changes.
628      * Note:  This is instantiated by incoming messages.
629      * Note:  We do not implement onPasswordFailed() because the default behavior of the
630      *        DevicePolicyManager - complete local wipe after 'n' failures - is sufficient.
631      */
632     public static class PolicyAdmin extends DeviceAdminReceiver {
633 
634         /**
635          * Called after the administrator is first enabled.
636          */
637         @Override
onEnabled(Context context, Intent intent)638         public void onEnabled(Context context, Intent intent) {
639             SecurityPolicy.getInstance(context).onAdminEnabled(true);
640         }
641 
642         /**
643          * Called prior to the administrator being disabled.
644          */
645         @Override
onDisabled(Context context, Intent intent)646         public void onDisabled(Context context, Intent intent) {
647             SecurityPolicy.getInstance(context).onAdminEnabled(false);
648         }
649 
650         /**
651          * Called after the user has changed their password.
652          */
653         @Override
onPasswordChanged(Context context, Intent intent)654         public void onPasswordChanged(Context context, Intent intent) {
655             SecurityPolicy.getInstance(context).clearAccountHoldFlags();
656         }
657     }
658 }
659